GoldMax, GoldFinder, and Sibot: Analyzing NOBELIUM’s layered persistence
Microsoft has identified three new pieces of malware being used in late-stage activity by NOBELIUM – the actor behind the SolarWinds attacks, SUNBURST, and TEARDROP.
Microsoft continues to work with partners and customers to track and expand our knowledge of the threat actor we refer to as NOBELIUM, the actor behind the SUNBURST backdoor, TEARDROP malware, and related components. As we stated before, we suspect that NOBELIUM can draw from significant operational resources often showcased in their campaigns, including custom-built malware and tools. In March 2021, we profiled NOBELIUM’s GoldMax, GoldFinder, and Sibot malware, which it uses for layered persistence. We then followed that up with another post in May, when we analyzed the actor’s early-stage toolset comprising EnvyScout, BoomBox, NativeZone, and VaporRage.
This blog is another in-depth analysis of newly detected NOBELIUM malware: a post-exploitation backdoor that Microsoft Threat Intelligence Center (MSTIC) refers to as FoggyWeb. As mentioned in previous blogs, NOBELIUM employs multiple tactics to pursue credential theft with the objective of gaining admin-level access to Active Directory Federation Services (AD FS) servers. Once NOBELIUM obtains credentials and successfully compromises a server, the actor relies on that access to maintain persistence and deepen its infiltration using sophisticated malware and tools. NOBELIUM uses FoggyWeb to remotely exfiltrate the configuration database of compromised AD FS servers, decrypted token-signing certificate, and token-decryption certificate, as well as to download and execute additional components. Use of FoggyWeb has been observed in the wild as early as April 2021.
Microsoft has notified all customers observed being targeted or compromised by this activity. If you believe your organization has been compromised, we recommend that you
Microsoft security products have implemented detections and protections against this malware. Indicators of compromise (IOCs), mitigation guidance, detection details, and hunting queries for Azure Sentinel and Microsoft 365 Defender customers are provided at the end of this analysis and in the product portals. Active Directory Federation Services (AD FS) servers run on-premises and customers can also follow detailed guidance on securing AD FS servers against attacks.
FoggyWeb is a passive and highly targeted backdoor capable of remotely exfiltrating sensitive information from a compromised AD FS server. It can also receive additional malicious components from a command-and-control (C2) server and execute them on the compromised server.
After compromising an AD FS server, NOBELIUM was observed dropping the following two files on the system (administrative privileges are required to write these files to the folders listed below):
FoggyWeb is stored in the encrypted file Windows.Data.TimeZones.zh-PH.pri, while the malicious file version.dll can be described as its loader. The AD FS service executable Microsoft.IdentityServer.ServiceHost.exe loads the said DLL file via the DLL search order hijacking technique that involves the core Common Language Runtime (CLR) DLL files (described in detail in the FoggyWeb loader section). This loader is responsible for loading the encrypted FoggyWeb backdoor file and utilizing a custom Lightweight Encryption Algorithm (LEA) routine to decrypt the backdoor in memory.
After de-obfuscating the backdoor, the loader proceeds to load FoggyWeb in the execution context of the AD FS application. The loader, an unmanaged application, leverages the CLR hosting interfaces and APIs to load the backdoor, a managed DLL, in the same Application Domain within which the legitimate AD FS managed code is executed. This grants the backdoor access to the AD FS codebase and resources, including the AD FS configuration database (as it inherits the AD FS service account permissions required to access the configuration database).
When loaded, the FoggyWeb backdoor (originally named Microsoft.IdentityServer.WebExtension.dll by its developer) functions as a passive and persistent backdoor that allows abuse of the Security Assertion Markup Language (SAML) token. The backdoor configures HTTP listeners for actor-defined URIs that mimic the structure of the legitimate URIs used by the target’s AD FS deployment. The custom listeners passively monitor all incoming HTTP GET and POST requests sent to the AD FS server from the intranet/internet and intercept HTTP requests that match the custom URI patterns defined by the actor. This version of FoggyWeb configures listeners for the following hardcoded URI patterns (which might vary per target):
Each HTTP GET/POST URI pattern above corresponds to a C2 command:
The diagram below illustrates the methodology used by the actor to communicate with the FoggyWeb backdoor located on a compromised internet-facing AD FS server.
Since FoggyWeb runs in the context of the main AD FS process, it inherits the AD FS service account permissions required to access the AD FS configuration database. This contrasts with tools such as ADFSDump that must be executed under the user context of the AD FS service account. Also, because FoggyWeb is loaded into the same application domain as the AD FS managed code, it gains programmatical access to the legitimate AD FS classes, methods, properties, fields, objects, and components that are subsequently leveraged by FoggyWeb to facilitate its malicious operations. For example, this allows FoggyWeb to gain access to the AD FS configuration data without connecting to the WID named pipe or manually running SQL queries to retrieve configuration information (for example, to obtain the EncryptedPfx blob from the configuration data). FoggyWeb is also AD FS version-agnostic; it does not need to keep track of legacy versus modern configuration table names and schemas, named pipe names, and other version-dependent properties of AD FS.
The file version.dll is a malicious loader responsible for loading an encrypted backdoor file from the file system, decrypting the backdoor file, and loading it in memory. This malicious DLL, which shares a name with a legitimate Windows DLL located in the %WinDir%\System32\ folder, is meant to be placed in the main AD FS folder %WinDir%\ADFS\, where the AD FS service executable Microsoft.IdentityServer.ServiceHost.exe is located (for reasons described later in this section).
When the AD FS service (adfssrv) is started, the service executable Microsoft.IdentityServer.ServiceHost.exe gets executed. As a .NET-based managed application, Microsoft.IdentityServer.ServiceHost.exe imports an unmanaged Windows DLL named mscoree.dll.
The file mscoree.dll dynamically loads another unmanaged Windows/CLR DLL named mscoreei.dll. As shown below, mscoreei.dll has a delay load import (Delay Import) named version.dll.
NOBELIUM, with existing administrative permissions, was observed to drop a malicious loader named version.dll in the %WinDir%\ADFS\ folder where the AD FS service executable Microsoft.IdentityServer.ServiceHost.exe is located. Once the system or the AD FS service is restarted, Microsoft.IdentityServer.ServiceHost.exe loads mscoree.dll, which in turn loads mscoreei.dll. As mentioned above, mscoreei.dll has a delay load import named version.dll. Once loaded, instead of loading the legitimate version.dll from the %WinDir%\System32\ folder mscoreei.dll loads the malicious version.dll planted by the attacker in %WinDir%\ADFS\ folder (referred to as DLL search order hijacking), as shown in the call stack below.
The malicious loader version.dll behaves as a proxy for all legitimate version.dll export function calls. As shown below, it exports the same 17 function names as the legitimate version of version.dll.
The export functions of the malicious version.dll are all short stubs that call a single trampoline function labeled TrampolineFunction, as seen in the screenshot below.
Below is a pseudocode for the trampoline function.
This trampoline function is responsible for the following:
The trampoline function preserves the value of the arguments/registers intended for the function from the legitimate version of version.dll by saving the value of certain CPU registers. It first pushes them onto the stack before calling the LoadDecryptExecuteBackdoor() function above and then restoring them before transferring execution to the function from the legitimate version of version.dll.
When called, LoadDecryptExecuteBackdoor() attempts to create a Windows event named {2783c149-77a7-5e51-0d83-ac0566daff96} to ensure that only one copy of the loader is actively running on the system. In a new thread, it then checks if the following file is present (hardcoded path string):
C:\Windows\SystemResources\Windows.Data.TimeZones\pris\Windows.Data.TimeZones.zh-PH.pri
Windows.Data.TimeZones.zh-PH.pri is an encrypted backdoor file that is placed in the folder above. MSTIC refers to this backdoor file as FoggyWeb, and our analysis is in the next section.
Microsoft.IdentityServer.ServiceHost.exe in and of itself is an unmanaged Windows executable that is generated when the high-level AD FS managed code is compiled. When executed, the unmanaged code inside Microsoft.IdentityServer.ServiceHost.exe leverages Common Language Runtime (CLR) to run the managed AD FS code within a virtual runtime environment. This virtual runtime environment is comprised of one or more application domains, which provide a unit of isolation for the runtime environment and allow different applications to run inside separate containers within a process. The managed AD FS code is executed within an application domain inside the virtual runtime environment.
The FoggyWeb backdoor (also a managed DLL) is intended to run alongside the legitimate AD FS code (that is, within the same application domain). This means that for the FoggyWeb loader to load the backdoor alongside the AD FS code, it needs to gain access to the same application domain that the AD FS code is executed within. Since the FoggyWeb loader version.dll is an unmanaged application, it cannot directly access the virtual runtime environment that the managed AD FS code is executed within. The loader overcomes this limitation and loads the backdoor alongside the AD FS code by leveraging the CLR hosting interfaces and APIs to access the virtual runtime environment within which the AD FS code is executed.
The loader performs the following high-level actions:
After decrypting each 16-byte cipher block, the loader uses the following XOR key to decode each individual decrypted/plaintext block:
This is equivalent to first LEA decrypting the entire file and then XOR decoding the decrypted data (instead of decrypting and XOR decoding each individual 16-byte block).
At this point in the execution cycle, the FoggyWeb DLL is loaded into one or more application domains where the legitimate AD FS code is running. This means the backdoor code runs alongside the AD FS code with the same access and permissions as the AD FS application. Because the backdoor is loaded in the same application domain as the AD FS code, it gains programmatical access to the legitimate classes, methods, properties, fields, objects, and components used by various AD FS modules to carry out their legitimate functionality. Such access allows the FoggyWeb backdoor to directly interact with the AD FS codebase (that is, not an external disk-resident tool) and selectively invoke native AD FS methods needed to facilitate its malicious operations.
This malicious memory-resident DLL (originally named Microsoft.IdentityServer.WebExtension.dll by its developer) functions as a backdoor targeting AD FS. It is loaded by the main AD FS service process Microsoft.IdentityServer.ServiceHost.exe through a malicious loader component.
When loaded, the backdoor starts an HTTP listener that listens for HTTP GET and POST requests containing the following URI patterns:
As shown below, the URI patterns are hardcoded in the backdoor and mimic the structure of the legitimate URIs used by the target’s AD FS deployment.
Once the backdoor receives an HTTP request that contains one of the URI patterns above, the listener proceeds to handle the request using either an HTTP GET or HTTP POST callback/handler method (ProcessGetRequest() and ProcessGetRequest(), respectively).
The incoming HTTP GET requests that contain the URI pattern /adfs/portalhttps://www.microsoft.com/images/theme/light01/ are handled by backdoor’s ProcessGetRequest() method.
If an incoming HTTP GET request is issued for a file/resource with the file extension of .webp, the ProcessGetRequest() method proceeds to handle the request. Otherwise, the request is ignored by the backdoor. Also, if the requested file name matches one of the three hardcoded names below, the backdoor treats the request as a C2 command issued by the attacker.
The following URL patterns are treated as C2 commands:
The first two C2 commands, profile.webp and background.webp (UrlGetFileNames[0] and UrlGetFileNames[1] in the screenshot above), are handled by calling the backdoor’s Service.GetCertificate() method.
As the name suggests, this method is responsible for retrieving an AD FS certificate (either the token- signing or the token encryption certificate, depending on the value of the certificateType parameter passed to the method) from the AD FS service configuration database.
Analyst note: Refer to the Appendix for an in-depth analysis of the Service.GetCertificate() method and how it obtains and decrypts either the token signing or encryption certificate.
As shown in the screenshot above, when the C2 command profile.webp (UrlGetFileNames[0]) is issued to the backdoor (by issuing an HTTP GET request for the URI /adfs/portalhttps://www.microsoft.com/images/theme/light01/profile.webp), the backdoor retrieves the token-signing certificate of the compromised AD FS server. Similarly, when the C2 command background.webp (UrlGetFileNames[1]) is issued to the backdoor (by issuing an HTTP GET request for the URI /adfs/portalhttps://www.microsoft.com/images/theme/light01/background.webp), the backdoor retrieves the token encryption certificate of the compromised AD FS server.
The third C2 command, logo.webp (UrlGetFileNames[2]), is triggered by sending an HTTP GET request to the following URI: /adfs/portalhttps://www.microsoft.com/images/theme/light01/logo.webp. The C2 command is handled by calling the backdoor’s GetInfo() method.
The GetInfo() method is responsible for dumping the AD FS service configuration data of the compromised server.
As shown above, the AD FS service configuration data is obtained via the ServiceSettingsData property, which retrieves the data from the AD FS service configuration database, Windows Internal Database (WID).
Before returning the output of the C2 commands (that is, the token-signing certificate, the token encryption certificate, or the AD FS service configuration data) to the C2 in an HTTP 200 response, the backdoor first obfuscates the output by calling its method named GetWebpImage().
The GetWebpImage() method is in charge of masquerading the output of the C2 commands as a legitimate WebP file (by adding appropriate RIFF/WebP file header magic/fields) and encoding the resulting WebP file.
GetWebpImage() uses the following helper methods to create and encode the fake WebP file that contains the C2 command output:
To compress the data, GetFrame() invokes the Common.Compress() method, which is used to compress the data by leveraging the C# GZipStream compression class.
For demonstration purposes, assume the C2 command yields the following data (a 256-byte pseudo-randomly generated byte array).
Given the data above (that is, sample C2 command output), GetFrame() returns the following byte array.
The array variable above contains the following 32-byte hardcoded RIFF/WebP header bytes.
If the size of the array passed to GetWebpHeader() (returned by GetFrame()) exceeds 8,192 bytes, the bytes at index 26 and 28 of the header bytes (initially set to 0x00) are replaced with 0x80. Otherwise, the bytes at index 26 and 28 are replaced with 0x40, as shown below.
GetWebpHeader() then returns the custom RIFF/WebP header above to GetWebpImage().
GetWebpImage() calls the Common.ProtectData() method of the backdoor to encode the portion of the new array that contains the compressed bytes (that is, it does not encode the custom RIFF/WebP header). As the second argument, GetWebpImage() passes the offset of the first compressed byte to ProtectData() (as shown in the table above, 0x20 or 32 is the offset of the first compressed byte in this case). ProtectData() uses a dynamic XOR key and a custom XOR methodology to XOR encode the compressed data.
Initially, the 12-byte hardcoded XOR key array contains the following (seed) bytes.
As shown in the screenshot above, each byte of compressed data is XOR’d with a byte from the XOR key array. The first byte of the compressed data (0x17) is XOR’d with the XOR key byte located at offset 8 of the key array (0x77).
After XOR’ing the first byte of the compressed data with the XOR key byte located at offset 8 of the key array, the XOR key byte itself gets overwritten with a new value.
For example, the XOR key byte located at offset 8 of the XOR key array (0x77) gets overwritten with 0xEE via the following operations.
The second byte of the compressed data (0x01) is XOR’d with the XOR key byte located at offset 9 of the key array (33 % 12 = 9) and so on until the key rolls to the first byte of the XOR array (as mentioned above, the XOR key bytes get overwritten after each encoding operation). Below is the XOR encoded version of the sample compressed array.
After the steps outlined above, GetWebpImage() returns the following sample data to the method that invokes it to obfuscate and conceal the output of each C2 command (ProcessGetRequest()).
As previously mentioned, ProcessGetRequest() returns the fake RIFF/WebP file generated above (containing stolen token-signing certificate, token encryption certificate, or the AD FS service configuration data) to the C2 in an HTTP 200 response.
If the backdoor cannot execute a C2 command successfully, it returns an HTTP 404 response to the C2 instead.
Incoming HTTP POST requests that match the URI pattern /adfs/services/trust/2005/samlmixed/upload are handled by the ProcessPostRequest() method.
This method ensures that the ContentType value of an incoming HTTP POST request ends with “xml” (case-insensitive), and the HTTP POST data contains two XML elements named X509Certificate and SignatureValue (for example, a blob that starts with the string “<X509Certificate>” and ends with the string “</X509Certificate>”).
If the XML data contains the two elements, the backdoor performs the following actions:
The UprotectData() method treats the first two bytes of the Base64 decoded value as a two-byte XOR key. It invokes the Common.ProtectData() method (covered in the previous section) on the rest of the data (that is, third byte on) and then uses the two-byte XOR key to XOR decode the data returned by Common.ProtectData(). In other words, UprotectData() leverages Common.ProtectData() to remove the first layer of XOR encoding and then another XOR routine to remove the second layer of XOR encoding applied to the data.
In a new thread, Service.ExecuteAssembly() calls Service.ExecuteAssemblyRoutine() method to handle the data.
If the decoded X509Certificate value does not start with MZ, the backdoor treats the decoded X509Certificate value as source code for a C#-based payload and calls its Service.ExecuteSource() method to dynamically compile and execute the payload in memory.
After handling the HTTP POST request containing the XML elements X509Certificate and SignatureValue, the backdoor responds to the request with an HTTP 204 response code. If the HTTP POST does not have the elements mentioned above, the backdoor responds to the request with an HTTP 404 response code.
As the name suggests, the Service.GetCertificate() method is responsible for retrieving an AD FS certificate (either the token- signing or the token encryption certificate, depending on the value of the certificateType parameter passed to the method) from the AD FS service configuration database.
The method performs the following actions to retrieve the desired certificate:
The AdministrationServiceState class/object contains configuration information necessary for the execution and handling of client requests. The field _state is used to maintain the current state of the AdministrationServiceState class/object (screenshot from Microsoft.IdentityServer.Service.dll).
The AdministrationServiceState object (stored in the _state field) contains another field named _certificateProtector.
The field _certificateProtector stores an instance of the Data Protector class DkmDataProtector for Distributed Key Management (DKM). The DkmDataProtector class implements a method named Unprotect(), which ultimately calls the Unprotect() method of DKM/IDKM (screenshot from Microsoft.IdentityServer.dll).
The DKM Unprotect() method inherits a method named Unprotect() from Microsoft.IdentityServer.Dkm.DKMBase (screenshot from Microsoft.IdentityServer.Dkm.dll).
The Unprotect() method from Microsoft.IdentityServer.Dkm.DKMBase (shown above) provides the functionality to decrypt the encrypted certificate (a PKCS12 object) stored in the EncryptedPfx blob.
Armed with the knowledge about the availability of the Unprotect() method accessible via the _certificateProtector field, the backdoor invokes the Unprotect() method to decrypt the encrypted certificate stored in the EncryptedPfx blob of the desired certificate type (either the AD FS token signing or encryption certificate).
A variant of the technique described in this Appendix was publicly presented by Douglas Bienstock and Austin Baker at the TROOPERS conference in 2019 (I am AD FS and so can you: Attacking Active Directory Federated Services). However, the method used by FoggyWeb differs from the publicly presented method, in that FoggyWeb leverages the _state and _certificateProtector fields from the AdministrationServiceState class/object to facilitate access to the Data Protector class DkmDataProtector (used to gain access to and invoke the Unprotect() method).
Type | Threat Name | Threat Type | Indicator |
MD5 | FoggyWeb | Loader | 5d5a1b4fafaf0451151d552d8eeb73ec |
SHA-1 | FoggyWeb | Loader | c896ece073dd01191cbc1d462bc2f47161828a83 |
SHA-256 | FoggyWeb | Loader | 231b5517b583de102cde59630c3bf938155d17037162f663874e4662af2481b1 |
MD5 | FoggyWeb | Backdoor (encrypted) | 9ff9401315d0f7258a9fcde0cfdef02b |
SHA-1 | FoggyWeb | Backdoor (encrypted) | 4597431f26424cb814c917168fa8d74d01ab7cd1 |
SHA-256 | FoggyWeb | Backdoor (encrypted) | da0be762bb785085d36aec80ef1697e25fb15414514768b3bcaf798dd9c9b169 |
MD5 | FoggyWeb | Backdoor (decrypted) | e9671d294ce41fe6dbb9637dc0157a88 |
SHA-1 | FoggyWeb | Backdoor (decrypted) | 85cfeccbb48fd9f498d24711c66e458e0a80cc90 |
SHA-256 | FoggyWeb | Backdoor (decrypted) | 568392bd815de9b677788addfc4fa4b0a5847464b9208d2093a8623bbecd81e6 |
Customers should review their AD FS Server configuration and implement changes to secure these systems from attacks:
We strongly recommend for organizations to harden and secure AD FS deployments through the following best practices:
Protecting AD FS servers is key to mitigating NOBELIUM attacks. Detecting and blocking malware, attacker activity, and other malicious artifacts on AD FS servers can break critical steps in known NOBELIUM attack chains. Microsoft Defender Antivirus detects the new NOBELIUM components discussed in this blog as the following malware:
Endpoint detection and response (EDR) capabilities in Microsoft Defender for Endpoint detect malicious behavior related to this malware which is surfaced as alerts with the following titles:
This kind of attack can also be detected in the cloud using Azure AD Identity Protection. It is recommended that you monitor the Azure AD Identity Protection Risk detections report for the “Token Issuer Anomaly” detection. This detection looks for anomalies in the SAML token presented to the Azure AD tenant.
To locate related activity, run the following advanced hunting queries in Microsoft 365 Defender:
DeviceImageLoadEvents
| where FolderPath has @"C:\Windows\ADFS"
| where FileName has @"version.dll"
Azure Sentinel customers can use the following detection queries to look for this activity:
Detection query: https://github.com/Azure/Azure-Sentinel/tree/master/Detections/MultipleDataSources/Nobelium_FoggyWeb.yaml
Indicator file: https://github.com/Azure/Azure-Sentinel/tree/master/Sample%20Data/Feeds/FoggyWebIOC.csv