Hunting Beyond Indicators - Part 2
Introduction
In part 1 of our two-part series, we discussed the benefits of hunting beyond indicators and focusing on TTPs. In the second part of this series, we will go over two example capabilities, KurtLar_SCADA and a strange .NET Modbus binary, both of which I discovered while hunting for Dragos. We will discuss the hunting hypotheses that led to their discoveries, and how hunting based on TTPs does not need to be complicated. We will cover some analysis of the capabilities and how they work under the hood. Further, the difficulties of making high-confidence analytical assessments will be demonstrated, highlighting the importance of “not crying wolf.”
Analyst Note: It’s worth mentioning that I’m a CTI analyst / researcher / threat hunter at Dragos, an industrial-focused cybersecurity company. As such, I’m strictly focused on hunting for ICS/OT threat activity, regardless of where, by whom, and against what. This is a rather broad focus, and not everything stated in this blog may apply to, say, an organization-specific threat hunter. I’m also extremely fortunate to have access to VT Enterprise. Not the cheapest data source!
Let’s jump right in!
KurtLar_SCADA
Heightened geopolitical tensions often increase the occurrence and tempo of cyber incidents. Analysts can benefit from keeping geopolitics in mind when generating their hunting hypothesis.
One day, I was searching VirusTotal for Python files with “SCADA” in the name that were submitted from Iran. It’s an incredibly simple query:
The hunting hypothesis in this case isn’t complicated, and can be boiled down into a simple sentence: “there exists malicious SCADA-related Python binaries uploaded from Iran.” Here, we are focused on MITRE ATT&CK T1059.006 - Command and Scripting Interpreter: Python. This is a rather generic TTP, but that’s not a bad thing when we already must meet the narrow requirement that the sample contain “SCADA” in its name.
Surprisingly, there was only one hit.
Analysis of KurtLar_SCADA
By pivoting in VirusTotal to explore similarly named files, I discovered an additional executable. This is a compiled Python file, a mechanism that bundles your Python code (in PYC format) and the Python interpreter (cpython) into a single binary. Luckily, several tools are available that can extract the bundled PYC and restore it to its original source code. However, KurtLar_SCADA.exe was written in Python 3.12, and there is currently no decompilation tool that actively supports v3.10+. We can view and analyze the Python bytecode ourselves, but this is time-consuming and tedious:
Thankfully, an AI transformer-based model was recently released called PyLingual. This tool inputs any valid PYC file and will output an approximation of its original source code. With the drop of a file, we now have a (roughly) correct approximation of KurtLar_SCADA’s Python source code.
This file is a glorified VNC client. The developer simply wrote a basic VNC authentication brute forcing script and aimed it at HMI’s (Human-Machine Interfaces), likely found via Shodan queries. While certainly not state-level “sophisticated” malware, KurtLar_SCADA had real-world impacts - several HMIs were defaced with a picture of Elliot from Mr. Robot (cringe). The operator even sold the tool in a Telegram channel, offering discounts to those targeting Israel or the U.S.
Here’s the execution flow of KurtLar_SCADA:
Threats don’t need to be “sophisticated” nor sexy, state-sponsored activity. The reality is that, especially in industrial sectors, low-hanging fruit remains effective. An opportunistic attacker can still cause damage, even if it’s not targeted or a groundbreaking technical capability.
In this hunt, did I find exactly what I was looking for? Yes and no. It’s not ICS-specific malware, as defined by Jimmy Wylie, but we did inform several impacted victims of their improperly exposed and compromised HMIs, partnering with CISA in the process, and informed the community of another fricken hacktivist group.
Check out my SANS webinar or my website for more technical details!
.NET Modbus Binary
As the FrostyGoop and PIPEDREAM malware proved, threat actors abuse open-source libraries for ICS protocols. Why write a custom Modbus client protocol stack when there are several already available?
With that in mind, I decided to collect key terms from a variety of popular Modbus libraries and create a Yara rule that identifies overlaps between these keywords and executable files. This targets another generic MITRE ATT&CK for ICS TTP: T0869 - Standard Application Layer Protocol. I ran a VirusTotal retrohunt, and it returned several hundred files.
This is a fairly wide collection - with hundreds of files, it becomes impossible to examine them one by one. So how do we triage these files?
Triaging False Positives
For those lucky enough with VirusTotal’s Enterprise access, you can view some of the matching strings by hovering over the “eye” icon.
This is very useful, but limited in functionality. Users have to manually go file-by-file, a time consuming, tedious, and boring process. Not exactly a realistic triage mechanism if you have hundreds of results returned. Even when hovering over the eye icon, you only see a subset of the matching strings. However, the idea behind this feature provides an analyst with reasoning into why their Yara rule matched. Did you really find malware targeting Emerson’s “Ovation”, or did your rule find a subset of the word “innovation,” a word commonly found in Windows binaries? This preview window is a great way to answer this question.
Analysts with basic programming skills can quickly write a Python script that iterates over each file, runs a Yara rule to find where the matches occurred, and then prints out a hexdump of the surrounding bytes, providing context as to why the rule matched in the first place.
In fact, this is an excellent way to quickly triage mass amounts of files matching a given Yara rule. By printing out the hexdump for each matching string, analysts will quickly find patterns - which keywords are too loose, which are too strict, and most critically, recurring matches which we can filter out en masse. In our “ovation” example, analysts may need to remove all “ovation” matching files or remove the word from the Yara rule and start collection again.
This technique focuses on removing known bads rather than finding true positives. You can try to find a needle in a haystack, but perhaps it’s more efficient to start removing hay from the pile than to start digging for the needle.
This methodology uncovered a really suspicious looking file that I’ve so creatively named the ‘.NET Modbus “Weirdness.”’
Analysis of .NET Modbus “Weirdness”
Thankfully, .NET can be decompiled into original source code with a tool like dnSpy, which makes our analysis significantly easier. I’ve renamed some of the obfuscated function symbols to be a bit more readable. Let’s take a peek and statically analyze the file.
Here, we have our main function with two critical lines: 13 and 18. On line 13, the sample creates a new ModbusClient using the EasyModbus library. This is the library that originally matched our Yara rule. Then, it attempts Modbus connection to the hostname “Ldug” on TCP/502. It then creates a new instance of the “Zsohv” class.
Let’s zoom into that class definition:
The first line of the Zsohv constructor creates an ArrayList object. This ArrayList is defined in the bottom box. It returns an array containing three elements: the first an Assembly object returned from a function I’ve named “GetSusDiscordPNG()”, the second an integer, and the third an obfuscated string.
Let’s take a look at the SusStuff class and the “GetSusDiscordPNG” function.
This function makes an HTTP request to a Discord CDN for an obfuscated executable file masquerading as a PNG. It then converts it from Base64 and performs a small character swap algorithm to revert it to a legitimate binary. Pretty straightforward, but also incredibly suspicious.
Looking back now at our Zsohv class, we obtain our ArrayList object, retrieve a specific type, then dynamically invoke a function named as our third element in the ArrayList object, “Uqokpnpmnzhx” (the name of the function to be executed).
This returns a string representing an IP address or hostname, which is then used to establish a Modbus connection—and that’s it! That’s all the executable does.
The behaviour of this sample is extremely suspicious. Making Modbus connections is not inherently malicious - it’s pretty normal ICS behavior. But obfuscating the binary’s symbols, hosting a second stage (masquerading as a PNG) from Discord’s CDN that retrieves the IP address of a Modbus server, and dynamically executing a specific function are absolutely not normal.
Unfortunately, at the time of analysis, the second stage was taken down from Discord, and it was never uploaded to VirusTotal, so we have zero visibility into what it could do or the exact IP/hostname to which it connects. Nothing further was discovered related to this sample - there are additional binaries which are obfuscated and behave in similar manners, but we were never able to tie them to in the wild activity. This could be a researcher’s pet project, the start of a new Modbus-capable malware, or who-knows-what. Certainly, there’s not enough evidence to claim it’s ICS malware.
In Conclusion
In each of the above cases, did we find ICS malware?
No, not at all.
However, these samples are exactly the type of thing we want to find. By focusing on behaviors rather than indicators, we can surface novel activity that isn’t yet known to the community. Hypothesizing based on behaviors doesn’t need to be complicated either. In our first example, we were simply hunting for Python scripts. In our second, we focused on a specific application layer protocol - Modbus. These are not crazy sophisticated TTPs nor do they require extreme technical knowledge. All it takes is an understanding of the threat landscape, forward thinking, and some creativity. This is an evidence-based approach based on behaviors from historical incidents. IOCs are easy, but they focus on where the fire was, not where the fire will be.
Happy hunting!
Hashes of samples:
KurtLar_SCADA:
da152d0c2d8cb61bee004ba93a329b76b6d1813ad89b6d6432869955ab08a4b8
1df6ea128bc9866f6564cfb3d01d3065469e3ca304b4a47a7d418c999af8889e
.NET Modbus “Weirdness”:
2fd3793f2d862c345d52e9db3d2ef1ec3af126b87542bbbed93d973869375cdb















