Introduction
A few days ago, I was analyzing a sample of Turla APT, but the sample using some techniques made the reversing and analyzing process harder.
This type of malwares use the CLR Hosting method to run malicious codes in memory without any footprint.
This technique can be used in other malwares to bypass AVs/EDRs and complicate the analysis process.
In this article, I want to analyze and describe the technique used by this sample to understand better and analyze this type of malwares.
Part1: Basic Analysis
Based on the PE analysis, we can find out that the sample is an x64 DLL file which is written in C/C++.
By looking at its export table, we see just two functions: ServiceMain and ControllHandler.
So the sample is a DLL file that should run as a service in Windows. (Because of ServiceMain)
When opening the malware in the IDAPro, we see it has an export function named “ServiceMain”, so we find out that the file is a service.
Also in strings of the sample, we see some suspicious strings.
For example, in this picture, we see a “PowershellRunner” string which can be a sign of using Powershell in this malware.
But we should dive in to find out the secrets.
Part2: Initializion
I use IDAPro for static analysis. After loading the file in the IDAPro, open the DllMain function.
In the DllMain function, we haven’t any special functionality. there is just a default code.
After that, we go to the ServiceMain Function.
In this function, we see some characters that chunked char by char. The malware uses this way to hide important strings like “Kernel32.dll” and “advapi32.dll”.
When the malware made library names, now uses GetModuleHandle and LoadLibrary APIs to check and load Kernel32 and Advapi dlls.
At first, it checks whether the module is loaded in the memory or not. If not, it loads it by LoadLibrary.
When the malware loaded needed libraries, now is time to resolve some APIs.
But the malware doesn’t do it very straight.
It first resolves GetProcAddress and then uses it to resolve other APIs. In this way, it can hide other straight calls to GetProcAddress and so the analyzer can’t detect it easily.
But here we have a problem. The malware’s needed API names are encrypted and we can’t find out which APIs are loaded by malware.
As you see, there is a function that, has three arguments. The handle of the Kernel32, the address of the GetProcAddress, and an array. I named it “resolve_apis”.
In the “resolve_apis” function we see many encrypted strings that rest in an array and are passed one by one to a function.
As you see, the encrypted strings are disunited character by character.
Tip: I labeled these strings after decrypting them manually.
The above picture shows the function that I named “decrypt_api_names“.
As you see the encrypted strings are sent to this function and the function decrypts them.
These encrypted strings are held in an array and decrypted in place.
At the end of this basic block, we see that the malware passes some decrypted strings to the “LoadLibrary” function. these libraries are: “Kernel32“, “msvcrt” and “rpcrt4“.
After that, the decrypted strings are sent to another function, which is the “GetProcAddress” pointer had been sent as an argument to the current function.
As you see, for example, the sprintf string, after decrypting is passed to this function. After the call to GetProcAddress, we see that the eax containing the resolved API’s address is saved in an array.
So, at the end of the “resolve_api” function, the malware is resolved all needed APIs and so on.
After the “resolve_api“, we see some calls to RegisterServiceCtrlHandlerW API and another function that calls SetServiceStatus API, and then the ServiceMain function is finished.
These functions are used to register the DLL in the service manager and tell the status of the service to it.
Part3: Analyzing the Main Functionality Of Malware and CLR Hosting
The main functionality of the malware is in a function at the 0x180007B07 address. I named this function “CreateThreads_and_pipe”.
This function is the one that contains the main functionality of the malware.
At the first of this function, the malware creates an RPC server over the pipe (ncacn_np type).
As is clear in the disassembled code, the pipe name is “\pipe\pnrsvc” which was disunited char by char.
So we find out the malware should act as a server and listen to requests that come from a series of clients and respond to them.
3-1: Searching for NullSessions Pipes
After creating the RPC server, we see a function at the 0x180007446 address. In summary, this function first resolves RegOpenKeyExW and RegCloseKey APIs from advapi.dll and then opens a key in the registry with the address “HKLM\SYSTEM\CurrentControlSet\services\LanmanServer\Parameters”.
In this path searches for a key named “NullSessionPipes” and queries for a special value, “pnrsvc” in this key.
Tip: In this function at the 0x1800070E9 address there is a function that resolves RegQueryValueExW API which is used to query the targeted key.
But why does the malware do this? The answer arises from a Vulnerability in Windows.
First I should describe the NullSessions.
When a user uses an application to access data or services on remote Win NT computers,
the user’s username, and password is sent to the remote machine to log in. If the user log-in is
unsuccessful, some applications try to log in using both the username and password
as a single null character. A session established in this manner is called a “Null Session” or anonymous.
As described, NullSessionPipes is a list of pipes that anonymous users can access to them.
So the malware wants to find out whether the pipe created before with the “pnrsvc” name, is in the anonymous pipes list, or not?
After this step, the malware creates two threads.
3-2: Setting ACL On The Pipe
As you see in the above picture, the first thread runs a function that I named “SetSecurityInfo_on_pipe”.
It is easy to analyze. It just uses CreateFile API to create the pipe and then sets some security descriptor on it.
This is the final code of the first thread
pipenameStr="\\.\pipe\pnrsvc";
pipe_handle = CreateFileW(pipenameStr, 0x1F01FFi64, 0i64, 0i64, 3, 0, 0i64);
SetSecurityInfo(pipe_handle);
ConvertStringSecurityDescriptorToSecurityDescriptorW(S:(ML;;NW;;S-1-16-0),1,&SecurityDescriptor,0);
GetSecurityDescriptorSacl(SecurityDescriptor,&v4,&pSacl,&lpbSaclDefaulted);
SetSecurityInfo(pipe_handle,6i64,16i64,0i64,0i64,0i64,pSacl);
3-3: Executing Malicious Scripts With CLR Hosting
The Next Thread is important. It is at address 0x180007495, which runs a function at address 0x180005850, which I named “execute_powershellscript”.
Let’s Analyze it.
At first, we see some disunited strings that are:
1- $p='HKLM:\SOFTWARE\Microsoft\Network\Media';$e=New-Object Text.ASCIIEncoding;$c=[Convert]::FromBase64String;$e.GetString($c.Invoke("$((gp $p).'Enable')"))|iex;$e.GetString($c.Invoke("$((gp $p).'(Default)')"))|iex
2- \NTUser.log
The first string is a Powershell script that reads the value of the “Default” of the “HKLM:\SOFTWARE\Microsoft\Network\Media” path and decodes it with base64 and then runs it.
Also, This function gets the value of the “PUBLIC” environment and makes the string “%PUBLIC%\NTUser.log”.
after a random sleep, we see another call to a function at 0x1800064EF.
In this function, the malware tries to find the “explorer.exe” process and its process ID.
If the funded process ID is a non-zero value, the malware continues the functionality. But if it can’t find the “explorer.exe”, In a loop tries until finds it. I think it is for checking whether Windows is started up completely or not. Because the malware runs as a service.
I named this function “get_explorer_pid“.
When it finds the explorer process, now goes to another function at address: 0x180006515.
As you see, the return value of this function I named “CreateDotNetHost_and_RunPowerShellPayload”, will write to the “%PUBLIC%\NTUser.log” file, and then the file will close and the function is finished.
So we should find out what happens in this function. It appears to run some commands due to writing outputs in the file.
Tip: As you see the PowerShell script that runs a script from the registry, is passed to this function.
3-4: CLR Hosting (Devil Hosting)
Now let’s go to this function.
At first, we see two strings that copy to two buffers, “PowerShellRunner” and “PowerShellRunner.PowerShellRunner”.
After that, we see a call to a function at “0x180004F01”. I named it “get_CLR_instance_handle”.
In this function, we see the malware first loads the “mscoree.dll” and we also see two strings: “v2.0.50727” and “v4.0.30319”.
But what is “mscoree.dll” and why does malware load it?
What is CLR Hosting
let’s talk about CLR. I first describe what is CLR.
The Common Language Runtime (CLR) is a component of the Microsoft .NET Framework that manages the execution of .NET applications. It is responsible for loading and executing the code written in various .NET programming languages, including C#, VB.NET, F#, and others.
When a C# program is compiled, the resulting executable code is in an intermediate language called Common Intermediate Language (CIL) or Microsoft Intermediate Language (MSIL). This code is not machine-specific, and it can run on any platform that has the CLR installed. When the CIL code is executed, the CLR compiles it into machine code that can be executed by the processor.
The CLR provides many services to .NET applications, including memory management, type safety, security, and exception handling. It also provides a Just-In-Time (JIT) compilation, which compiles the CIL code into machine code on the fly as the program runs, optimizing performance.
The CLR engine is part of the “mscoree.dll” which is a library file that is essential for the execution of “managed code” applications written for use with the .NET Framework.
Based on the above thoughts, when you want to run a managed code (for example C#) in an unmanaged code (C/C++), you should use CLR. So you should load “mscoree.dll” in your unmanaged code and use it for running the .NET code.
This way is named, “CLR Hosting”.
Now we found out why the malware loads the “mscoree.dll”. It needs to run managed code(dotNet codes).
Now I want to describe the process of CLR hosting.
3-5: Getting a CLR Instant
Return to the malware. At the address 0x180004B72 we see a function that the handle of mscoree.dll, the string “v4.0.30319” and a pointer passed to it. This function tries to create an instant of the CLR engine based on dotnet framework version 4.
The summary of this function is:
CLRCreateInstance(&CLRMetaHost,&IID_ICLRMetaHost,&metaHostaddr);
metaHostaddr->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfoaddr);
runtimeInfoaddr->IsLoadable(&boolvalue)
runtimeInfoaddr->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);
runtimeInfo->Release();
metaHostaddr->Release()
return runtimeHost
The above code is the initializing part of the CLR Hosting process.
1- Create a CLR MetaHost instance
2- Get RuntimeInfo using “GetRuntime” to get a Runtime of a specific version of dot net
3- Then get an interface for communicating with the CLR engine and fill the runtimeHost argument with the runtime handle.
If this function successfully runs and can get a handle of CLR, it means that dotnet framework version 4 is installed on the target Windows. But if it fails and cannot get a CLR handle, the malware now runs another function at address “0x180004B91” and this time sends the handle of mscoree.dll, the string “v2.0.50727” and a pointer.
This function like the previous, tries to get a handle on the CLR engine based on the dotnet framework version 2. The summary of this function is:
CorBindToRuntime("v2.0.50727","srv",CLSID_CorRuntimeHost,IID_ICorRuntimeHost,&runtimeHost);
return runtimeHost;
The above code is the process of getting a handle on dotnet version 2 runtime. It’s simpler than the previous code.
Anyway at the end of “get_CLR_instance_handle”, the malware will get a handle to a CLR engine whether version 4 or 2.
3-6: Executing The Payload
Now back to the “CreateDotNetHost_and_RunPowerShellPayload”. After the malware successfully creates a handle for the CLR engine, now it can do CLR hosting.
In the “CreateDotNetHost_and_RunPowerShellPayload” function, we see some functions that are indirect calls. They are like a vTable that every time the malware calls a method of them.
As you can see there is a series of calls that continues to the end of the function.
Now here we have the second part of the CLR Hosting process.
runtimeHost->Start()
runtimeHost->GetDefaultDomain(&clrAppDomain)
clrAppDomain->QueryInterface(IID_PPV_ARGS(&clrAppDomainInterface))
clrAppDomainInterface->Load_3(MZ_FilePayloadArray, &clrHandle)
clrHandle->GetType_2("PowerShellRunner.PowerShellRunner", &clrtype)
The above code is the second part of the CLR Hosting process. I labeled these functions as comments in the IDA. But in disassembled code, you can’t detect them without knowing the process of CLR Hosting.
The important part of this code is the “Load_3” function. In this phase, we should pass our C# binary as a safe array to this function to load it.
As you can see in the above picture, first a safe array is created, and then its value fills with an array that I named MZ_File_payload, an array of data of an MZ binary file.
After that, we see that the value of the ecx is 0x2200 which is the size of the dotnet file.
Then this safe array is passed to the Load_3 function to load this managed code in the CLR engine.
This is the target binary array that malware wants to run under CLR. We know the size of this binary (0x2200 or 8704). So we can dump this array into a file.
After dumping the dotnet file, when decompiling it, you can see there is a function named “InvokePS“.
Now in the last phase of CLR Hosting, the malware should pass the code that wants to run.
For this purpose, it should pass the function name of the dotnet binary file and the argument that should pass to it.
As you see, the Powershell script which had passed to the “CreateDotNetHost_and_RunPowerShellPayload” as an argument, and the “InvokePS” string, is passed to another function named “InvokePS_PowerShellScript_and_write_to_file” which is the last part of the CLR Hosting process.
clrtype->InvokeMember_3("InvokePS",static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),NULL,vtEmpty,poweshellScript,&ReturnVal);
Now the Powershell Script that is filled by the “HKLM:\SOFTWARE\Microsoft\Network\Media” key, is sent to the InvokePS function of the dotnet managed code and then run in there and the output of it will be written in the log file.
Ok, finally we arrived at the end of the CLR Hosting part.
Now we find out that the second thread that runs the “execute_powershellscript” function, first checks for the explorer.exe process, then creates an instant of the CLR engine, runs the PowerShell script, and then writes the output of the script in the log file in the %PUBLIC% path.
The analysis is complete at this point. In this article, I have delved into the topic of CLR Hosting and its potential for malicious exploitation. It is now possible to identify and detect malware that executes a malicious payload directly in the system’s memory with this technique.
IOCs
Sample:
Turla APT
RPC-Backdoorsha256:454e6c3d8c1c982cd301b4dd82ec3431935c28adea78ed8160d731ab0bed6cb7
The dotnet payload Hash:
Sha256: 44c4b33dbdbaa8e002fcfa2429e51995d86e208c9bd49c6dc15e8948f46d9354
References
https://www.geeksforgeeks.org/common-language-runtime-clr-in-c-sharp
Could you upload the malware sample? I can’t find it.
tnx
No sorry.
it was the most in-depth analysis I had ever seen.
You wrote very well and understandably.
thank you
Thank you for sharing this article.
But I didn’t understand the RPC usage in the malware. When uses it?