This is some text inside of a div block.
Glitch effect

Bullseye: A Story of a Targeted Cyberattack


Download Your

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Glitch effectGlitch effectGlitch effectGlitch effectGlitch effect

Bullseye: A Story of a Targeted Cyberattack

Glitch effectGlitch effectGlitch effect
Glitch banner

At Huntress, we work to understand hackers’ nefarious activities and analyze a lot of malware. 

And I mean, a lot of malware. 

At the time of writing, the Huntress ThreatOps team has responded to and sent incident reports for 103,872 investigations. That’s more than one hundred thousand! 😱

This story explores one particularly interesting investigation because from everything that we uncovered, it really looks like a targeted attack.

Keep reading for an in-depth analysis—with videos!—of persistence in action.

What Did Huntress Find?

We will dive into the technical analysis and walk you through the malware sample in just a moment, but let me give you a run-down of why this one is so cool. 😈

Well—at least if you happen to think malware and hacker tradecraft can even be “cool” in a strange way:

  • Interesting technique to spread out a large PowerShell payload in multiple chunks
  • Use of a typosquatted domain website to look like the real organization
  • Masquerades as a legitimate vendor solution, a product the target organization actually uses

After we unravel each stage of the malware and put the puzzle pieces together, we find that this prepares command-and-control functionality with an Empire beacon—Empire being a very common offensive PowerShell framework.

How Did Huntress Find This?

Huntress looks for persistent footholds—what I tend to explain to people as an implant, or the means for a hacker to maintain access to a victim's computer. Typically, I use the term “backdoor” because I think that’s the easiest word for people to understand. That isn’t the most accurate representation of what we mean by persistence, but it sets the groundwork.

Persistence is intentionally set up by the bad guys. They know they can’t phish the same victim a second time, or exploit the same vulnerability because that’s loud and overt—so they install a small, stealthy piece of code that gives them access from a distance.

This gives the hackers an advantage because they can lurk in the environment however long they would like to, and then easily get back to compromising the target. But truthfully, this is a double-edged sword… persistence mechanisms, by nature, have to be persistent. That means they remain on the computer somehow, someway, and the bad guys leave behind artifacts.

That gives us, the good guys, an advantage just as well. If we track down the persistent implants hackers are using, we can be certain that there is malicious activity. Defenders can then work as detectives to understand what the hackers have been up to. Ultimately, we can remove the persistence and shut out the hackers.

Persistence is the smoking gun at the scene of the crime. 

Finding persistence means we can force the hackers to earn every inch of their access.

Where Was the Persistence?

In this article, we will focus on the targeted attack and sample discussed previously. (For a greater overview on persistence and other crafty techniques used by bad actors, check out our Persistence Knowledge Kit.)

Scheduled Task Overview

We found this peculiar scheduled task, named “LTSvc,” present in this file location:


The scheduled task looked to be invoking PowerShell. When we see PowerShell being started by an autorun, we take extra precaution because sometimes the code could be a benign script developed by a system administrator, or a tool an organization legitimately uses.

In this case, however, it was easy to see strange behavior that no sysadmin would put in their environment.

Screen Shot 2021-08-21 at 1.37.17 PM

Full script can be found online in this gist.

An observant eye will immediately notice that this looks like PowerShell code. We can, of course, see that this invokes PowerShell with a hidden window style, bypassing the execution policy, running without a profile and passing a lengthy command as an argument.

Before we jump in and work to understand what this code is doing, let’s take a step back and help make this easier on our eyes.

As it stands right now, this code is very hard for a human analyst to read. The syntax is crammed all in one line, but there don’t seem to be any tricks used to “obfuscate” or hide segments of the code… it just needs to be cleaned. 

The process of “cleaning” to look more readable is often called “beautifying” or “prettifying.” Doing this manually can be time-consuming and tedious, but sometimes it is the best method.

Oftentimes there are online utilities that can automate this process, but personally, I haven’t found a reliable avenue for PowerShell code specifically. You can, however, try to beautify PowerShell syntax within Visual Studio Code with a formatter extension (this sometimes misbehaves for me) or the local Edit-DTWBeautifyScript tool put together by Dan Ward.

From my testing, I had not seen either Visual Studio Code or the Edit-DTWBeautifyScript clean up this specific script with all the syntax crammed onto one line. I manually cleaned the script like so:

  • Adding a newline character following every semi-colon (;)
  • Adding a newline and tab indentation after opening logic blocks (i.e., a curly brace { )
  • Adding whitespace and newlines for easier visual flow

With that complete, we now have a much more readable PowerShell script that we can begin to understand.


The first thing the PowerShell code does is define a large array or list of “Models”, seemingly different types of computer processors. This variable, “$Models”, is not used elsewhere in this code, but it could perhaps be used later… so we will keep it in mind.

Following that long list of CPU names, we see some “if statements” or conditionals that are the usual boilerplate syntax to prepare communication between HTTPS websites. This makes sense, as the very next segment of code looks to request different online web pages over and over again.

The (New-Object Net.WebClient).DownloadString() function call will reach out to an external webpage and return out all of the contents. We see these calls piped to Invoke-Expression and subsequently piped to Out-Null.

Invoke-Expression will execute and evaluate the data passed in as real PowerShell code. It is the equivalent of an “eval” statement in other languages, where the raw string supplied will then be invoked and run as a new part of the program. Invoke-Expression is often seen as its alias form, “IEX”, which is just a shorthand nickname for the same cmdlet.

This technique of repeated DownloadString and Invoke-Expression initially looked like an interesting “brute force” method to retrieve the next payload, but as I began to download each new snippet for my own analysis, it was clear that these were just one small piece of the puzzle.

Why Do You Consider This "Targeted"?

Please note, the domain you see present in this code is something that we have intentionally placed in the code to protect customer information. Originally, this was in fact a look-alike website that would masquerade as the original organization and business. 

This domain—and the malicious code itself—would make repeated reference to one specific product or legitimate software solution that this company really used in their technology stack. Considering this seemed to be inside information for the organization, it is clear the hackers did their homework. They wanted this malware to blend in and camouflage with the real operations of that specific business.

What Was the Next PowerShell Code?

After downloading all of the repeated referenced DownloadString endpoints for new .ps1 code, this syntax came to light:

This is the full code that was segmented into different pieces and downloaded with the original LTSvc stage. We can begin to beautify and deobfuscate this as usual.

This new PowerShell uses repeated Base64 encoding to try and hide the indicators of using Win32 API function calls, namely:

Following these, we see a very large blob of Base64 encoded data. There is one unique telltale here, as the beginning characters are “TVqQ”... this is one rendition of how an executable Windows binary might look when Base64 encoded!

Carving out and decoding that first blob of Base64 data, we can certainly determine this is a Windows executable. In fact, it is a DLL… and a .NET assembly!

Screen Shot 2021-08-21 at 1.49.42 PM

Being a .NET assembly, we can open this file in tools like dotPeek, dnSpy, or ILSpy to see what the original source code was. Thankfully we don’t need to open up a debugger or disassembly like GHIDRA or IDA because, due to the nature of a .NET assembly, we can pry out the source code.

On the REMnux distribution, you can extract this with the pre-installed command-line version of ILSpy, ilspycmd.

This C# code looks to be just a small stub, loading in and importing those same three functions from kernel32.dll. This class is named ASBBAPI, and we can keep that in mind, as we may certainly see that as we continue on through the PowerShell code.

What this means is that the first Base64 blob is this compiled DLL, which will then be reflectively loaded into the running PowerShell context. We see that here, on line 5:


At this point, the code has staged the use of these three Win32 API functions.

What Does the PowerShell Code Do With These API Calls?

In the next few lines of the PowerShell code, we can see that it cleverly creates an object from this ASBBAPI class defined in the C# source. With that defined, it can call those LoadLibrary, GetProcAddress and VirtualProtect functions with arguments that it supplies and tries to obfuscate with repeated [char] type casting.

Watching how this plays out, we see the code...

  • Load the amsi.dll library into a variable with LoadLibrary
  • Find the AmsiScanBuffer function with GetProcessAddress
  • Make the memory space of AmsiScanBuffer writable with VirtualProtect

Finally, some new unique bytes (0xB8, 0x57, 0x00, 0x07, 0x80 and 0xC3) are written into the memory space and essentially clobber what once was the AmsiScanBuffer function. This is done with one of the very last lines in that PowerShell code:

[System.Runtime.InteropServices.Marshal]::Copy($LHLCD, 0, $WUGAGU, 6)

Those new bytes are the machine code operations to “return out,” or just do nothing and successfully pass on the execution.

This process ultimately breaks AMSI.

What Is AMSI?

If you aren’t familiar, AMSI is the “Antimalware Scan Interface.” It is a security mechanism put in place within modern versions of the Windows operating system that looks for nefarious or malicious code within .NET assemblies, PowerShell code or other native languages.

Essentially, before a line of code is executed, it is checked with your antivirus and determined if it is “good or evil.” If the engine determines that it looks to be malicious code, execution is blocked.

The functionality of AMSI resides in the native amsi.dll library—which is precisely why it was loaded by our PowerShell sample. The core of AMSI and the real workerbee that makes it tick is the AmsiScanBuffer function—which is precisely why the PowerShell sample selects that function and overwrites it.

This is not a new or novel technique. There is a lot of previous research and even online utilities to quickly generate a payload to break AMSI just like this. You can experiment within your own virtual machine if you would like, testing syntax from While I was doing some testing, I found that a lot of generated PowerShell payloads were not successful in bypassing AMSI, but this specific technique (using a C# stub to load in the Win32 API calls) was successful.

You can see the effect of this in the live demonstration video below.

What Happens Next?

So far, the PowerShell code we have examined has bypassed AMSI in this running context. That means that any malicious or nefarious code in PowerShell could run undetected, without the prying eyes of antivirus or preventive security solutions.

Finally, we reach the last line of this PowerShell sample. As you could have guessed, this includes yet another large chunk of Base64 encoded data, dubbed as a variable named $a. This data is then decoded, decompressed as a GZIP archive, and ultimately passed to IEX… that alias and shorthand nickname for the dubious Invoke-Expression.

Once again, we have another “stage” or layer of this malware sample as we unpeel the onion to uncover what this PowerShell code truly does.

This next stage is yet again a minified and compressed one-liner, with random capitalization and yet more Base64 encoding. I once again manually beautified/deobfuscated it, resulting in this:

The very first line, $ErrorActionPreference = "SilentlyContinue";, simply tells PowerShell to ignore and move on from any errors in this script.

Then the code begins to build out functionality to make even more web requests. It defines a new WebClient object and supplies a custom User-Agent. Whenever you access a website with your web browser like Firefox or Chrome, you inherently include in your request a configurable “User-Agent” header that identifies what browser you are using. By default, PowerShell web requests would include its own de facto User-Agent… but changing this to look like a more standard, typical web browser would again try to blend into normal traffic.

I have renamed the variables here so $c2_server was originally called $ser, but this again references the look-alike domain. We see a new endpoint, “/login/product.php”.... however, the name “product” was the name of the real software solution that this organization actually used. Again, this information is redacted for customer confidentiality.

The latter half of the PowerShell code looks to prepare some form of encrypted communication with this endpoint. It downloads the data from the page, unravels it with the use of the $K (key), $R, $D, $S and $J variables, and ultimately again passes this to IEX to run yet more PowerShell.

When we first found this malware sample, the C2 website was still live and accessible. We would neuter the malicious IEX call, replacing it with essentially a simple Write-Host, so we could let PowerShell run this code for us and then display what this next PowerShell stage would execute.

What Is the Final Stage of PowerShell Code?

While we see another IEX call present in this code, unfortunately, we were unable to retrieve any more payloads for further analysis. It seems that the listening beacon was no longer active—and we can now explore why we say that.

This final stage of PowerShell code is a bit lengthy, so for brevity’s sake it is accessible in this gist and we will share a snippet in the screenshot below.

Screen Shot 2021-08-21 at 2.04.41 PM

The top half of the code prepares functions named Start-Negotiate, ConvertTo-RC4ByteStream and Decrypt-Bytes, and once again stages functionality to make encrypted web communications. It performs some light reconnaissance to gather the victim’s computer operating system, user account and IP address.

Near the end of the code, you may see one damning piece of intel. The malicious PowerShell runs the cmdlet Invoke-Empire.

Just above that, we see yet another IEX call… but this is the segment we were unable to retrieve more data out of. It’s easy to assume that whatever was pulled down and executed in that call would prepare and build out the functions for Empire, so the “Invoke-Empire” cmdlet would successfully run.

Blurry glitch effect

Sign Up for Blog Updates

Subscribe today and you’ll be the first to know when new content hits the blog.

Huntress at work