In partnership with vx-underground, SentinelOne recently ran its first Malware Research Challenge, in which we asked researchers across the cybersecurity community to submit their research to showcase their talents and bring their insights to a wider audience.
In today’s post, Millie Nym demonstrates a problem-solving approach to reverse engineering a malware sample, highlighting not just the practical steps taken but also the logical reasoning conducted as the investigation unfolded. The post offers a fascinating insight into how researchers tackle the challenges in front of them and a perfect example for anyone wishing to learn or develop their reverse engineering skills.
In this post, I will be going over my process of analyzing a sample of ArechClient2. Including initial analysis, deobfuscation and unpacking of the loader. Followed by the analysis of the .NET payload revealing its config and C2 information.
It began with this tweet by @Gi7w0rm. They mentioned me and a few others asking for help analyzing this sample. I decided to look into the sample. After publishing some threat intel and a few updates on my progress on Twitter, I decided to write this report for a more detailed documentation of my analysis. The original sample can be found here.
The sample consists of two files, an executable and an a3x file. After some quick research, I found that a3x is a “compiled” form of AutoIt script. The executables icon is the logo of AutoIt and the copyright information says it’s AutoIt. This leads me to believe that this executable is the runtime required to execute the a3x file.
I ran the file in a Windows Sandbox for some quick intel and immediately got a Windows Defender hit for MSIL:Trojan, which indicates that this AutoIt part is just a loader for a second stage .NET binary. In case you are not familiar with the terms, “MSIL” stands for Microsoft Intermediate Language, which is the bytecode that .NET binaries are compiled to.
The a3x script is human-readable, so after putting it into Visual Studio Code I saw this.
It looks pretty messy at first but taking a closer look I found something that stuck out: The calls to the function called DoctrineDrama look suspiciously like string decryption. So my next step was to find that function. I used the search function to look for its name until I found the actual implementation. All functions start with the keyword Func and end with the keyword EndFunc, making it easy to identify them. I copied the code of the DoctrineDrama function to a separate file. The code is obfuscated and seems to contain some junk code. My first step was to indent the code for easier readability.
Looking at the switch cases inside the loops, I realized that only the branches that use ExitLoop are of importance. Taking a look at the switch conditions confirmed that suspicion. At the beginning of the function, the second variable is the loop condition, initialized with a value of 921021. Looking at the switch, it matches the case that exits the loop, meaning the other cases are dead code and can be ignored. I removed the dead branches, cleaned up the unnecessary loops and got rid of the unused variables:
After cleaning up we are left with this code. Reading this we can deduce some more fitting variable names, the first argument seems to be the encrypted input, and the second argument is the key. The first variable is the resulting string.
To understand the rest of the code I looked at the documentation of AutoIt. The StringSplit function takes the following arguments:
a delimiter char
an optional argument for the delimiter search mode
So the second local variable in DoctrineDrama is an array of strings split from the input.
Next, the code iterates through all the elements of that array and appends a new character to the output string with every iteration. We see a call to a function called Chr, which according to documentation converts a numeric between 0-255 value to an ASCII character. But something is off, what is going on inside that call to Chr? Subtraction on a string, how does that work? I wondered about that but after a quick web search, I found out that in AutoIt digit strings seem to be auto-converted to a number if you perform any arithmetic operation on them. Once the loop is finished, the output string is returned.
Looking at this fully cleaned-up version, I reimplemented the decryption routine in C# to build a simple deobfuscator.
static string Decrypt(string input, int key)
var buffer = input.Split(‘h’);
var builder = new StringBuilder();
for (int i = 0; i
The deobfuscator uses a simple regex pattern to match every call to DoctrineDrama and replace it with the decrypted string. It also outputs a list of all decrypted strings. The full deobfuscator code can be found here.
Dumping the Payload
After deobfuscating all the strings, I searched the string dump for some Windows API function names that I would expect from a loader. I found a few hits on NtResumeThread, CreateProcessW and NtUnmapViewOfSection. These three in combination give a huge hint towards process hollowing. After searching the string dump for .exe I found the suspected injection target Microsoft.NETFrameworkv4.0.30319jsc.exe, a utility of .NET Framework 4.x which comes with every standard Windows 10 install.
My next step was to debug the executable using x64Dbg. I set a breakpoint on CreateProcessW, to ensure we break before the injection process is started. After running past the entry point I was greeted with this nice little message.
The message box claims I violated a EULA which I never read nor agreed to. I guess we can’t debug the malware any further, how unfortunate. Luckily for us, x64Dbg has a built-in AutoIt EULA bypass, it’s called Hide Debugger (PEB). You can find it under Debug>Advanced>Hide Debugger (PEB). Make sure to run x64Dbg in elevated mode.
After dealing with the rather simple anti-debug, we let it run. When debugged, the executable spawns a file dialog asking for an a3x file, when run without a debugger it automatically finds the script file. After pointing it to the script file, we let it run until the breakpoint for CreateProcessW is hit. At this point, jsc.exe will be started in suspended mode.
Checking Process Explorer confirms that the decrypted path from the AutoIt script was indeed the injection target. We add another breakpoint on NtResumeThread, which will break execution after the injection is finished but before the thread is resumed to execute the malware.
Since we already know the malware is .NET-based I will use ExtremeDumper to get the managed payload from the jsc.exe process. Run ExtremeDumper as admin and dump jsc.exe, if it does not show up make sure you are using the x86 version of ExtremeDumper.
At the time of writing, the loader does not run anymore but fails with an error message about Windows updates. Sifting through the string dump I suspect there is some sort of date check that prevents further execution. This was likely implemented to prevent future analysis. Luckily I had dumped the actual payload before.
The .NET Payload
After dumping the loader, I had to deal with the managed payload. The image is heavily obfuscated. I started my hunt in the <Module> class, also referred to as the global type. I start by checking this class since its constructor is called before the managed entry point. Many obfuscators call their runtime protections or functions like string decryption here.
My guess was correct, I found a string decryption method c in <Module> (token 0x06000003). The method reads the encrypted string data from an embedded resource and then performs a single XOR operation decryption on it. The key used for decryption is supplied via parameters, which leads me to believe that each string has a unique decryption key.
After checking references to c it turned out that the decryption relies on flow-dependent variables. The calls to the decryption routine have encrypted arguments that are using several opaque predicates and global variables that are initialized and changed depending on call flow.
This means we would have to emulate or solve all calculations required to obtain the local variables and global fields that are used by the expressions that decrypt the arguments of the call to our decryption method c. The additional dependency on call flow further increases the effort required since we would need to solve all calculations in every method in the correct order. Considering all this I ditched the idea of writing a static string decryption tool.
Sifting through the binary I found quite a few similarities to Redline, both making use of DataContracts and async tasks for the separate stealer modules.
One class in particular seemed interesting. After looking for networking related functions I found a class cj token 0x0200010C that connects to a server via .NET’s TcpClient. Looking at the code we can spot the use of another class called xj, which seems to contain the IP and port number for the TCP connection. See line 155 tcpClient.Connect(xj.c, Convert.ToInt32(xj.a.d)
Apart from that, xj also seems to contain a URL that the malware accesses and downloads a string from, see line 168. Let’s take a closer look at xj token 0x02000107. It contains quite a few properties, but the most interesting is the constructor.
This looks like a potential config class. It initializes the properties used for the initial TCP connection and the string download we saw in cj, which is a good indicator that we are indeed looking at the malware’s config.
I placed a breakpoint at the end of the constructor. Since the string decryption method was still an issue, the easiest way to get the strings was to run the binary and have it decrypt the strings for me. I debugged the executable using dnSpy until I hit the breakpoint at the end of the constructor. After the breakpoint hit, we can view all the properties and fields values in the Locals window by expanding the this parameter.
Here we see the C2 IP 22.214.171.124 and port 15647. We can also see a Pastebin link that caught my interest: The paste contains another IP 126.96.36.199, potentially a fallback C2.
Before debugging, I modified the string decryption method by adding a few lines to write every decrypted string to disk. This modification makes it so that instead of immediately returning the string it’s first passed to AppendAllText and written to a file of our choice.
The dump revealed the same values that we found in the Locals window and a few more strings of interest. For example, we got a list of the paths that the stealer checks for potential credentials. The main targets of this stealer seem to be browsers, mail clients and game clients like Steam. This is similar to most mainstream stealers. You can view the full-string dump here.
Speaking of strings, I noticed another similarity to Redline, the use of char array to string conversion at runtime. Although Redline in many cases does insert some additional junk into these arrays that is removed from the constructed string, using the Replace or Remove method.
Due to the heavy obfuscation and the rather similar behavior to existing stealers, I decided to not investigate this payload further. We revealed the most important IOCs and got a pretty good understanding of the stealer’s targets.
Identification of ArechClient Malware Family
After analyzing the string dump, I found some indicators that could help with attribution to a certain malware family. Although this sample does look very similar to Redline stealer, it is actually not part of that family. I found this blob of data that looked suspiciously like C2 communication:
Referencing the above data and the port number to other writeups, like this one from IronNet Threat Research, revealed similarities to a different malware family. The screenshot below shows a network capture of an active ArechClient2 sample performed by the researchers from IronNet. Comparing this data we can conclude that our sample is also part of the ArechClient2 family.
We found that the initial loader was implemented in AutoIt and uses Process Hollowing to load a .NET-based payload, we reconstructed the string decryption method enabling us to partially deobfuscate the loader. We dumped the managed payload using a debugger and ExtremeDumper. We analyzed and debugged the managed payload to reveal the payload config, containing the C2 information.
Readers can find more of Millie’s work on GitHub.
Indicators of Compromise
Potential Fallback C2
URL for fallback C2
.NET payload Test.exe
AutoIt loader 45.exe
AutoIt script S.a3x
This post is licensed under CC BY 4.0 by the author.