-
Notifications
You must be signed in to change notification settings - Fork 65
Issues Editing Rdp.cs File #272
Replies: 1 comment · 7 replies
-
Hi, thanks for checking out the project. Did you recompile? I ask this because you stated, "the client does not read this new Rdp.cs file." C# is a compiled language, so you will have to compile a new binary in order to change the behavior to include saving a file. I can include this in a new client version as configurable behavior, but I can't do this today. |
Beta Was this translation helpful? Give feedback.
All reactions
-
I tried experimenting with .NET and visual studio but its a bit out of expertise from there lol. I'll just wait for the new client version. Thanks for the help. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Roger, I will try to incorporate this into the v8 beta that I am currently working on. Please standby. |
Beta Was this translation helpful? Give feedback.
All reactions
-
I am looking at this now, but in comparing the current version of rdp.cs and your version, it appears that something greatly changed the formatting of your file, so I can't really see what lines you specifically changed. Can you tell me where you made specific edits? |
Beta Was this translation helpful? Give feedback.
All reactions
-
So sorry for the late reply, ill try to point out the changes as best as I can:
using System;
using System.Diagnostics;
using System.Threading;
using Ghosts.Client.Infrastructure;
using Ghosts.Domain;
using Ghosts.Domain.Code;
using WorkingHours = Ghosts.Client.Infrastructure.WorkingHours;
using AutoItX3Lib;
using System.Text;
using Newtonsoft.Json;
using System.IO;
using NPOI.SS.Formula.Functions;
namespace Ghosts.Client.Handlers
{
/// <summary>
/// Remote Desktop Protocol Handler
/// Action: The logged-in user (i.e. ghosts user) opens a remote desktop to randomly chosen target, and does random
/// mouse movements for the specified ExecutionTime. When this time has elapsed, the connection is closed,
/// another target is chosen, and the cycle repeats.
///
/// The credential file is the same format as WMI, SFTP, SSH, etc.
/// If only the password is specified, then the login user is assumed to be the current user.
/// If username is specified (and optionally domain), and the current user is not the logged in user,
/// then a login using the specified domain\username is used.
///
/// See the sample Rdp timeline for all options.
///
/// This handler will supply a password (and also usename if supplied) if prompted, and dismiss a untrused certificate check prompt if one
/// appears.
///
/// It is assumed that the logged-in user (or the specified user) has the right to log into the target system.
/// Caveat: This has only be tested in a Windows domain system where the domain policy was altered to allow all users
/// remote desktop priviledges, and only tested using RDP to a domain controller server.
/// It has also been tested using the Domain Admin user (not the current logged in user), in which the handler
/// supplied both username and password to the crdential prompts. Using the Domain Admin user does not require
/// modification of the domain policy to enable RDP.
///
/// </summary>
public class Rdp : BaseHandler
{
private int MouseSleep = 10000;
private Credentials CurrentCreds = null;
private int ExecutionTime = 20000;
private int ExecutionProbability = 100;
private int JitterFactor = 0; //used with Jitter.JitterFactorDelay
private string CurrentTarget;
public Rdp(TimelineHandler handler)
{
try
{
base.Init(handler);
if (handler.Loop)
{
while (true)
{
Ex(handler);
}
}
else
{
Ex(handler);
}
}
catch (ThreadAbortException)
{
Log.Trace("Rdp closing...");
}
catch (Exception e)
{
Log.Error(e);
}
}
public void Ex(TimelineHandler handler)
{
if (handler.HandlerArgs != null)
{
if (handler.HandlerArgs.ContainsKey("CredentialsFile"))
{
try
{
this.CurrentCreds = JsonConvert.DeserializeObject<Credentials>(File.ReadAllText(handler.HandlerArgs["CredentialsFile"].ToString()));
}
catch (Exception e)
{
Log.Error(e);
}
}
if (handler.HandlerArgs.ContainsKey("mouse-sleep-time"))
{
int.TryParse(handler.HandlerArgs["mouse-sleep-time"].ToString(), out MouseSleep);
if (MouseSleep < 0) MouseSleep = 10000;
}
if (handler.HandlerArgs.ContainsKey("execution-time"))
{
int.TryParse(handler.HandlerArgs["execution-time"].ToString(), out ExecutionTime);
if (ExecutionTime < 0) ExecutionTime = 5 * 60 * 1000; //5 minutes
}
if (handler.HandlerArgs.ContainsKey("execution-probability"))
{
int.TryParse(handler.HandlerArgs["execution-probability"].ToString(), out ExecutionProbability);
if (ExecutionProbability < 0 || ExecutionProbability > 100) ExecutionProbability = 100;
}
if (handler.HandlerArgs.ContainsKey("delay-jitter"))
{
JitterFactor = Jitter.JitterFactorParse(handler.HandlerArgs["delay-jitter"].ToString());
}
///created "CreateFileRemote" string withing Ex method
string CreateFileCommand = null;
if (handler.HandlerArgs.ContainsKey("create-file-command"))
{
Log.Trace($"RDP:: create-file-command is readable");
CreateFileCommand = handler.HandlerArgs["create-file-command"].ToString();
} else
{
Log.Trace($"RDP:: create-file-command is not readable");
}
}
//arguments parsed. Enter loop that does not exit unless forced exit
while (true)
{
foreach (var timelineEvent in handler.TimeLineEvents)
{
WorkingHours.Is(handler);
switch (timelineEvent.Command)
{
case "random":
default:
if (ExecutionProbability < _random.Next(0, 100))
{
//skipping this command
Log.Trace($"RDP choice skipped due to execution probability");
Thread.Sleep(Jitter.JitterFactorDelay(timelineEvent.DelayAfter, JitterFactor));
continue;
}
if (timelineEvent.DelayBefore > 0)
{
Thread.Sleep(timelineEvent.DelayBefore);
}
var choice = timelineEvent.CommandArgs[_random.Next(0, timelineEvent.CommandArgs.Count)];
if (!string.IsNullOrEmpty(choice.ToString()))
{
this.RdpEx(handler, timelineEvent, choice.ToString(), createFileCommand);
}
Thread.Sleep(Jitter.JitterFactorDelay(timelineEvent.DelayAfter, JitterFactor));
break;
}
}
}
}
public void Cleanup(AutoItX3 au, string caption,Process process)
{
//close window if still open
var winHandle = Winuser.FindWindow("TscShellContainerClass", caption);
if (winHandle != IntPtr.Zero)
{
Log.Trace($"RDP:: Closing Remote Desktop window.");
au.WinClose(caption);
Thread.Sleep(1000);
// handle the close dialog
checkGenericPrompt(au, "{ENTER}");
Thread.Sleep(1000);
}
if (process != null)
{
if (!process.HasExited)
{
Log.Trace($"RDP:: Closing Remote Desktop process window.");
process.CloseMainWindow();
Thread.Sleep(10000);
if (!process.HasExited)
{
Log.Trace($"RDP:: Killing Remote Desktop process.");
process.Kill();
Thread.Sleep(1000);
}
} else
{
Log.Trace($"RDP:: Remote Desktop process has exited.");
}
}
else
{
Log.Trace($"RDP:: No Remote Desktop process to cleanup.");
}
}
public void RdpEx(TimelineHandler handler, TimelineEvent timelineEvent, string choice)
{
Log.Trace($"Attempting RDP Connection");
char[] charSeparators = new char[] { '|' };
var cmdArgs = choice.Split(charSeparators, 2, StringSplitOptions.None);
var target = cmdArgs[0];
CurrentTarget = target;
string command = $"mstsc /v:{target}";
var credKey = cmdArgs[1];
var domain = CurrentCreds.GetDomain(credKey);
var username = CurrentCreds.GetUsername(credKey);
var password = CurrentCreds.GetPassword(credKey);
Log.Trace($"RDP:: Spawning RDP connection for target {target}");
var localUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
bool usePasswordOnly = true;
if (username != null)
{
if (domain != null)
{
username = $"{domain}\\{username}";
}
if (!localUser.ToLower().Contains(username.ToLower())) {
usePasswordOnly = false;
}
}
var processStartInfo = new ProcessStartInfo("cmd.exe")
{
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false
};
AutoItX3 au = new AutoItX3();
var caption = $"{target} - Remote Desktop Connection";
using (var process = Process.Start(processStartInfo))
{
try
{
Thread.Sleep(1000);
if (process != null)
{
process.StandardInput.WriteLine(command);
process.StandardInput.Close(); // line added to stop process from hanging on ReadToEnd()
Thread.Sleep(2000); //give time for response
checkPasswordPrompt(au, password, username, usePasswordOnly);
//wait 3 minutes for connection window
if (!findRdpWindow(target, 3))
{
//may have a certificate problem
checkGenericPrompt(au, "{TAB}{TAB}{TAB}{ENTER}"); //this is for a certificate prompt
}
//try again for another 3 minutes to find
if (findRdpWindow(target, 3))
{
Log.Trace($"Successfully connected to {target} via RDP.");
if (!string.IsNullOrEmpty(createFileCommand))
{
// Call the method to create a file
CreateFileOnRemote(au, createFileCommand);
}
doMouseLoop(caption, target, au);
}
else
{
Log.Trace($"RDP:: Unable to connect to remote desktop for {target}");
}
Thread.Sleep(2000);
Cleanup(au, caption,process);
}
else
{
Log.Trace($"RDP:: Failed to execute the remote desktop connection program for {target}");
}
}
catch (ThreadAbortException)
{
Cleanup(au, caption, process);
throw; //pass up
}
catch (Exception e)
{
Cleanup(au, caption, process);
Log.Error(e);
}
}
}
public bool findRdpWindow(string target,int minutes)
{
var caption = $"{target} - Remote Desktop Connection";
var winHandle = Winuser.FindWindow("TscShellContainerClass", caption);
int count = 0;
//wait for window to appear
while (winHandle == IntPtr.Zero)
{
Log.Trace($"RDP:: Unable to find desktop window for {target}, sleeping 1 minute");
Thread.Sleep(60 * 1000); //sleep 1 minute, then try again
count++;
if (count >= minutes) break;
winHandle = Winuser.FindWindow("TscShellContainerClass", caption);
}
return winHandle != IntPtr.Zero;
}
public void doMouseLoop(string caption, string target, AutoItX3 au)
{
var winHandle = Winuser.FindWindow("TscShellContainerClass", caption);
if (winHandle != IntPtr.Zero)
{
Log.Trace($"RDP:: Connected to {target}, beginning activity loop");
// found the remote desktop window
int totalTime = 0;
while (totalTime < ExecutionTime)
{
var sleepTime = Jitter.JitterFactorDelay(MouseSleep, JitterFactor);
Thread.Sleep(sleepTime);
totalTime += sleepTime;
//move the mouse
Winuser.SetForegroundWindow(winHandle);
Winuser.RECT rc;
Winuser.GetWindowRect(winHandle, out rc);
au.MouseMove(rc.Left, rc.Top, 30);
au.MouseMove(_random.Next(rc.Left, rc.Right), _random.Next(rc.Top, rc.Bottom), 30);
Winuser.SetForegroundWindow(winHandle);
}
//close the window
Log.Trace($"RDP:: Finished activity loop for {target}.");
au.WinClose(caption);
Thread.Sleep(1000);
// handle the close dialog
checkGenericPrompt(au, "{ENTER}");
Thread.Sleep(1000);
}
else
{
Log.Trace($"RDP:: Unable to find remote desktop window for {target}.");
}
}
/// <summary>
/// Escape special characters in string sent by sendkeys
/// </summary>
/// <param name="password"></param>
/// <returns></returns>
public static string escapePassword(string password)
{
char[] special = { '+', '^', '%', '(', ')', '~', '{', '}', '[', ']' };
var s = password;
foreach (var c in special)
{
var cstr = c.ToString();
if (s.Contains(cstr))
{
var rep = "{" + cstr + "}";
s = s.Replace(cstr, rep);
}
}
return s;
}
public IntPtr findDialogWindow(string caption)
{
var winHandle = Winuser.FindWindow(null, caption);
if (winHandle != IntPtr.Zero)
{
//this finds any window with this caption
//determine if class name is for standard dialogue
int bufSize = 256;
StringBuilder buffer = new StringBuilder(bufSize);
Winuser.GetClassName(winHandle, buffer, bufSize);
var s = buffer.ToString();
if (s.Contains("32770")) return winHandle; //success
}
return IntPtr.Zero;
}
public void checkGenericPrompt(AutoItX3 au, string closeString)
{
var caption = "Remote Desktop Connection";
var winHandle = findDialogWindow(caption);
if (winHandle != IntPtr.Zero)
{
//this is a standard dialog class. By default, chooses 'No'. Select yes prompt
Winuser.SetForegroundWindow(winHandle);
au.Send(closeString); //send the close string
Thread.Sleep(1000);
Log.Trace($"RDP:: Found window prompt, caption: {caption} for {CurrentTarget}.");
}
return;
}
public void checkPasswordPrompt(AutoItX3 au, string password, string username, bool usePasswordOnly)
{
bool foundWinSecurity = false;
var winHandle = Winuser.FindWindow("Credential Dialog Xaml Host", "Windows Security");
if (winHandle == IntPtr.Zero)
{
//try harder
foundWinSecurity = true;
winHandle = findDialogWindow("Windows Security");
}
if (winHandle != IntPtr.Zero)
{
if (usePasswordOnly)
{
string responseString;
if (foundWinSecurity)
{
responseString = "{TAB}{TAB}{ENTER}"; //this form has a different response string
} else
{
responseString = "{TAB}{TAB}{TAB}{ENTER}";
}
//password prompt is up. handle it.
Winuser.SetForegroundWindow(winHandle);
var s = escapePassword(password);
System.Windows.Forms.SendKeys.SendWait(s); //fill in password field
au.Send(responseString);
Thread.Sleep(1000);
Log.Trace($"RDP:: Found password prompt for {CurrentTarget}.");
} else
{
//try entering full username
Winuser.SetForegroundWindow(winHandle);
if (foundWinSecurity)
{
au.Send("{DOWN}"); //moves down to next user
Thread.Sleep(1000);
Winuser.SetForegroundWindow(winHandle);
System.Windows.Forms.SendKeys.SendWait(username); //fill in user field, tab to password field
au.Send("{TAB}");
var s = escapePassword(password);
System.Windows.Forms.SendKeys.SendWait(s); //fill in password field
au.Send("{ENTER}");
Thread.Sleep(1000);
}
else
{
au.Send("{TAB}{TAB}{ENTER}"); //this clicks on 'More Choices'
Thread.Sleep(1000);
Winuser.SetForegroundWindow(winHandle);
au.Send("{TAB}{TAB}{ENTER}"); //this clicks on 'Use diff user'
Thread.Sleep(1000);
Winuser.SetForegroundWindow(winHandle);
System.Windows.Forms.SendKeys.SendWait(username); //fill in user field, tab to password field
au.Send("{TAB}");
var s = escapePassword(password);
System.Windows.Forms.SendKeys.SendWait(s); //fill in password field
au.Send("{ENTER}");
Thread.Sleep(1000);
}
Log.Trace($"RDP:: Found user/password prompt for {CurrentTarget}.");
}
}
return;
}
///Create file remote method
public void CreateFileOnRemote(AutoItX3 au, string command)
{
Log.Trace("Attempting to create a file on remote...");
try
{
Log.Trace($"Running command: {command}");
au.Run("notepad.exe");
Thread.Sleep(1000); // Adjust based on expected application startup time
Log.Trace("Notepad opened. Activating Notepad window...");
au.WinActivate("Untitled - Notepad");
Thread.Sleep(500);
Log.Trace($"Typing command/content into Notepad: {command}");
au.Send(command);
Thread.Sleep(500);
Log.Trace("Saving the file...");
au.Send("^s"); // Ctrl + S to open Save As dialog
Thread.Sleep(1000);
// Assuming the command contains the full path including file name
Log.Trace($"Setting file path: C:\\ghosts_data\\cap.txt");
au.Send("C:\\ghosts_data\\cap.txt");
Thread.Sleep(500);
au.Send("{ENTER}"); // Press Enter to save
Thread.Sleep(1000); // Wait for any potential save dialog
Log.Trace("Closing Notepad...");
au.Send("^w"); // Ctrl + W to close Notepad
au.Send("{ENTER}"); // Confirm save if needed
Log.Trace("File creation process completed successfully.");
}
catch (Exception ex)
{
Log.Error($"Failed to create file on remote: {ex.Message}");
}
}
}
} |
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
Ok, one more question — what is I can't compile and don't see any indication in your file as to what this should be. |
Beta Was this translation helpful? Give feedback.
-
I have been editing the Rdp.cs file to include an option for creating and saving a Notepad file while in the RDP session (before the random mouse movements). I changed the Rdp.cs file on my Ghosts VM, which runs the docker containers. The issue I am having is it appears the client does not read this new Rdp.cs file. The access time for the Rdp.cs file is not altered when running the ghosts.exe file on the client. I have tried editing the "Log.Trace($"RDP:: Connected to {target}, beginning activity loop");" line to "Log.Trace($"RDP:: Connected to {target}, beginning activity loop TEST_TEST_TEST");". When looking at the app.log file on the client, the original message is displayed (not TEST_TEST_TEST). I have tried restarting both client and Ghosts VM, but have had no luck in the new Rdp.cs file being read. Any help is appreciated. I have posted the Rdp.cs file and timeline.json file as a reference.
Rdp.cs
Timeline.json
Beta Was this translation helpful? Give feedback.
All reactions