Analysis of Native Process CLR Hosting Used by AgentTesla

By

Overview

SonicWall Capture Labs threat research team has observed fileless .Net managed code injection in a native 64-bit process.  Native code or unmanaged code refers to low-level compiled code such as C/C++.  Managed code refers to code that is written to target .NET and will not work without the CLR (Microsoft .NET engine) runtime libraries. The injected code belongs to AgentTesla malware.

Technical Analysis

The initial infection vector is a Word document that the client received as an email attachment. Upon opening this document, it will ask the user to enable a VBA macro. If enabled, this VBA macro downloads a 64-bit executable from the internet and executes it.

The downloaded binary is a 64-bit, Rust-compiled binary. We are focusing on the techniques used by this binary to inject the malicious AgentTesla payload into its own process memory using CLR Hosting.

The following are details of the 64-bit downloaded executable file.

MD5 : 4521162D45EFC83FA76C4B5C0D405265

SHA256 :  F00ED06A1D402ECF760EC92F3280EF6C09E76036854ABACADCAC9311706ED97D

URL from which 64-bit executable downloaded:

https[:]//New-Coder[.]cc/Users/signed_20240329011751156[.]exe

Disabling Event Tracing for Windows (ETW)

On execution of the Rust binary, it patches the “EtwEventWrite” API from NTDLL using the NtProtectVirtualMemory, WriteProcessMemory and FlushInstructionCache APIs.

Figure 1:  After the malware patches the “EtwEventWrite” API

This 64-bit malware process downloads an encoded shellcode from the following URL which contains the AgenetTesla payload.

URL of the shellcode:

https[:]//New-Coder[.]cc/Users/shellcodeAny_20240329011339585[.]bin

Next, the malware starts the execution of the downloaded shellcode using the “EnumSystemLocalesA” API by passing the address of the shellcode to the API as the callback function argument.

Figure 2: Moved shellcode from read-write memory to executable memory and starts its execution

The shellcode parses PEB and PEB_LDR_DATA to resolve the API dynamically. It will resolve the VirtualAlloc, VirtualFree, and RtlExitUserProcess APIs using an API hashing technique.

Next, the shellcode allocates read-write memory using the “VirtualAlloc” API and moves 0x3E3C0 bytes from the shellcode to the allocated memory.  These bytes are the encoded AgentTesla payload.

Figure 3: Moved shellcode data in read-write memory and starts decryption routine

As shown in Figure 3 above, the first 4bytes (DWORD) are the size of encoded data followed by encoded data.

Next, it proceeds to decrypt the payload. The shellcode uses a customized decryption routine where it performs single-byte XOR decryption in a loop, and for every iteration, it decrypts 0x10 bytes in the payload with a 0x10-byte encryption key. In a decryption loop, every time the malware uses a different encryption key derived from a combination of XOR and arithmetic operations. It decrypts the 0x3E184 bytes of the memory buffer to get the final payload.

Figure 4: Single-byte XOR decryption

Next, the shellcode reads the DLL name array, which contains the names of DLLs that are required for the malware to perform its operation. This array is “ole32;oleaut32;wininet;mscoree;shell32”.

The shellcode parses the PEB structure to check for the presence of the above-mentioned DLLs in the loaded modules list and loads the DLL using the “LoadLibraryA” API if they are not present.

Once the required DLLs are loaded into memory, it resolves a few more APIs such as “VirtualProtect”, “SafeArrayCreate”, “CLRCreateInstance” etc., using the API Hashing technique.

AMSI Bypass Using Memory Patching

Next, the shellcode patches the “AmsiScanBuffer” and “AmsiScanString” API, as shown below.

Figure 5: “AmsiScanBuffer” API after patching

Figure 6: “AmsiScanString” API after patching

Disabling Event Tracing (2nd time)

We have observed the second time patching in shellcode to disable Event Tracing, this might be to confirm the patching continues. It patches “EtwEventWrite” API with a single byte “0xCC” (return instruction).

Next, the shellcode starts CLR hosting.

These are the steps required to perform CLR Hosting, in order:

  • Create a CLR MetaHost instance:

ICLRMetaHost* pMetaHost = NULL;

CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);

  • Enumerate the installed runtimes:

pMetaHost->EnumerateInstalledRuntimes(&installedRuntimes);

Enumerate through runtimes and try to locate a specific dotnet version installed on the system.

One has to use “GetVersionString” method from the ICLRRuntimeInfo interface to find the supported .NET Framework version.  This .NET Framework version string will be passed to the GetRuntime API.

  • Get RuntimeInfo using “GetRuntime”:

ICLRRuntimeInfo* runtimeInfo = NULL;

pMetaHost->GetRuntime(sz_runtimeVersion, IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo);

  • Get ICorRuntimeHost interface:

ICorRuntimeHost Interface allows more control over the managed runtime from the native code, It can be retrieved using ICLRRuntimeInfo::GetInterface

ICorRuntimeHost* pCorRuntimeHost =NULL;

runtimeInfo->GetInterface(CLSID_CorRuntimeHost,IID_ICorRuntimeHost,(LPVOID*)& pCorRuntimeHost);

  • Retrieve the default AppDomain for the current process:

ICorRuntimeHost interface allows retrieval of the default AppDomain for the current process.

IUnknown* appDomainThunk;

pCorRuntimeHost->GetDefaultDomain(&appDomainThunk);

_AppDomain* defaultAppDomain = NULL;

appDomainThunk->QueryInterface(IID_AppDomain, &defaultAppDomain);

  • Create SafeArray:

we must create SafeArray and copy the MSIL payload to this SafeArray since we can’t provide an unmanaged byte array to the “Load_3” method which loads the assembly into the app domain.

SAFEARRAYBOUND bounds[1];

bounds[0].cElements = sizeof (rawAssemblyByteArray);

bounds[0].lLbound = 0;

SAFEARRAY* safeArray = SafeArrayCreate(VT_UI1, 1, bounds);

SafeArrayLock(safeArray);

memcpy(safeArray->pvData, rawAssemblyByteArray, sizeof (rawAssemblyByteArray));

SafeArrayUnlock(safeArray);

  • Load the assembly to the AppDomain:

_AssemblyPtr  managedAssembly = NULL;

defaultAppDomain->Load_3(safeArray, &managedAssembly)

  • Find an entry point to the loaded assembly:

_MethodInfoPtr  pMethodInfo = NULL;

managedAssembly->get_EntryPoint(&pMethodInfo)

  • Call the entry point:

pMethodInfo->Invoke_3(VARIANT(), SafeArray_Pointer_To_Arguement , &VARIANT())

The second parameter for the “Invoke_3” function is the SafeArray pointer to the arguments that will be passed to the MSIL payload.

ShellCode Executing Managed Code from a Native Code Using CLR hosting

Next, the shellcode calls the “CLRCreateInstance” API from mscoree.dll. The CLRCreateInstance API returns the new CLR MetaHost instance which will be used by malware to prepare a runtime so it can execute the MSIL AgentTesla payload in memory.

We can see in the below figure that multiple GUIDs have been used while retrieving CLR Hosting Interfaces, for e.g., to retrieve “ICorRuntimeHost” interface, it passed “CLSID_CorRuntimeHost” ,  “IID_ICorRuntimeHost” as an argument to the “GetInterface” API.

Figure 7: GUID used while CLR hosting

Next, the shellcode retrieves the ICorRuntimeHost interface and starts the CLR.

Figure 8: Call to GetInterface API to retrieve the ICorRuntimeHost interface

Figure 9: Call start method from ICorRuntimeHost interface to start CLR

Next, the shellcode retrieves the default app domain for the current process, as shown below.

Figure 10: Retrieve the default AppDomain for the current process.

Next, the shellcode creates SafeArray using the “SafeArrayCreate“ API by passing an argument as the size of managed code which is 0x3CC00. This SafeArray does have a pointer to the buffer where malware copies the MSIL payload.

Figure 11: Create a SafeArray and copy AgentTesla payload to it

Once a SafeArray was created, it could be loaded into an AppDomain with the “Load_3” method, this “Load_3” method gives a pointer to an Assembly object.

Figure 12:  Calls “Load_3” method to load the SafeArray into AppDomain

Next, the shellcode zeros out the MSIL payload from the region where it got decrypted then it destroys the SafeArray using the “SafeArrayDestroy” API.

Finally, the shellcode retrieves the entry point for the assembly and calls the “Invoke_3” method to start the 32-bit MSIL AgentTesla process within the context of the 64-bit native process.

Figure 13: Starts the MSIL AgentTesla process

Figure 14: Browser folder enumerated by 64-bit process once the fileless managed code injection has been done

In Figure 14 above, it looks like the 64-bit process is enumerating the browser folder, but its AgentTesla malware started its execution within the .NET engine.

SonicWall Protections

SonicWall Capture Labs provides protection against analyzed 64-bit executable (4521162d45efc83fa76c4b5c0d405265) as GAV: MalAgent.QZ (Trojan).

This threat was also detected by SonicWall Capture ATP w/RTDMI.

The initial infection vector which is a Word document file has been detected by SonicWall Capture ATP w/RTDMI.

IOCs

Document file:

MD5 : D99020C900069E737B3F4AB8C6947375

SHA256 : A6562D8F34D4C25A94313EBBED1137514EED90B233A94A9125E087781C733B37

64-bit downloaded executable:

MD5 : 4521162D45EFC83FA76C4B5C0D405265

SHA256 : F00ED06A1D402ECF760EC92F3280EF6C09E76036854ABACADCAC9311706ED97D

Shellcode blob:

MD5 : CD485BF146E942EC6BB51351FA42B1FF

SHA256 : 02C03E2E8CA28849969AE9A8AAA7FDE8A8B918B5A29548840367F3ECAC543E2D

Injected AgentTesla Payload:

MD5 : 6999D02AA08B56EFE8B2DBBD6FDC9A78

SHA256 : 7B6867606027BFCA492F95E2197A3571D3332D59B65E1850CB20AA6854486B41

URLs used by malware:

https[:]//New-Coder[.]cc/Users/signed_20240329011751156[.]exe  (64-bit exe downloaded)

https[:]//New-Coder[.]cc/Users/shellcodeAny_20240329011339585[.]bin (shellcode downloaded)

Security News
The SonicWall Capture Labs Threat Research Team gathers, analyzes and vets cross-vector threat information from the SonicWall Capture Threat network, consisting of global devices and resources, including more than 1 million security sensors in nearly 200 countries and territories. The research team identifies, analyzes, and mitigates critical vulnerabilities and malware daily through in-depth research, which drives protection for all SonicWall customers. In addition to safeguarding networks globally, the research team supports the larger threat intelligence community by releasing weekly deep technical analyses of the most critical threats to small businesses, providing critical knowledge that defenders need to protect their networks.