From d56a84c6c2f82c47c375bfc7e6941b1b8e6b99ec Mon Sep 17 00:00:00 2001 From: 23langloisj <23langloisj@gmail.com> Date: Wed, 22 May 2024 15:11:40 -0400 Subject: [PATCH] Adds ghosts lite client --- src/Ghosts.Client.Lite/.gitignore | 319 +++++++++ src/Ghosts.Client.Lite/Ghosts.Client.Lite.sln | 16 + src/Ghosts.Client.Lite/README.md | 41 ++ src/Ghosts.Client.Lite/lib/ghosts.domain.dll | Bin 0 -> 76288 bytes src/Ghosts.Client.Lite/scripts/build.sh | 23 + .../src/Ghosts.Client.Lite.csproj | 49 ++ .../src/Infrastructure/ApplicationDetails.cs | 191 ++++++ .../src/Infrastructure/Comms/CheckId.cs | 152 +++++ .../Comms/ClientSocket/BackgroundTaskQueue.cs | 34 + .../ClientSocket/ClientSocketConnection.cs | 154 +++++ .../Comms/ClientSocket/QueueEntry.cs | 18 + .../src/Infrastructure/Comms/Updates.cs | 433 ++++++++++++ .../Infrastructure/Handlers/FileHandler.cs | 132 ++++ .../Infrastructure/Handlers/HttpHandler.cs | 186 ++++++ .../Handlers/MemoryCleanUpHandler.cs | 15 + .../src/Infrastructure/Orchestrator.cs | 48 ++ .../src/Infrastructure/RandomFilename.cs | 64 ++ .../src/Infrastructure/Services/LogWriter.cs | 24 + .../src/Infrastructure/WebClientHeaders.cs | 52 ++ src/Ghosts.Client.Lite/src/Program.cs | 172 +++++ .../src/config/application.json | 25 + .../src/config/timeline.json | 616 ++++++++++++++++++ src/Ghosts.Client.Lite/src/nlog.config | 26 + 23 files changed, 2790 insertions(+) create mode 100644 src/Ghosts.Client.Lite/.gitignore create mode 100644 src/Ghosts.Client.Lite/Ghosts.Client.Lite.sln create mode 100644 src/Ghosts.Client.Lite/README.md create mode 100644 src/Ghosts.Client.Lite/lib/ghosts.domain.dll create mode 100755 src/Ghosts.Client.Lite/scripts/build.sh create mode 100644 src/Ghosts.Client.Lite/src/Ghosts.Client.Lite.csproj create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/ApplicationDetails.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Comms/CheckId.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/BackgroundTaskQueue.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/ClientSocketConnection.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/QueueEntry.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Comms/Updates.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Handlers/FileHandler.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Handlers/HttpHandler.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Handlers/MemoryCleanUpHandler.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Orchestrator.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/RandomFilename.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/Services/LogWriter.cs create mode 100644 src/Ghosts.Client.Lite/src/Infrastructure/WebClientHeaders.cs create mode 100644 src/Ghosts.Client.Lite/src/Program.cs create mode 100644 src/Ghosts.Client.Lite/src/config/application.json create mode 100644 src/Ghosts.Client.Lite/src/config/timeline.json create mode 100644 src/Ghosts.Client.Lite/src/nlog.config diff --git a/src/Ghosts.Client.Lite/.gitignore b/src/Ghosts.Client.Lite/.gitignore new file mode 100644 index 00000000..694d46f4 --- /dev/null +++ b/src/Ghosts.Client.Lite/.gitignore @@ -0,0 +1,319 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +*.DS_Store + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs +ghosts.tools.emailgenerator/config/email-content.csv +ghosts.tools.emailgenerator/config/email-content - Copy.csv +# Ghosts.Client/config/email-reply.csv +# Ghosts.Client/config/email-content.csv +vtunnel.exe + +_db/ +_g/ + +_data/ +_data/* +*_data/* + +_config/ +_config/* +*_config/* + +_output/ +_output/* +*_output/* +output/ \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/Ghosts.Client.Lite.sln b/src/Ghosts.Client.Lite/Ghosts.Client.Lite.sln new file mode 100644 index 00000000..1b1b5938 --- /dev/null +++ b/src/Ghosts.Client.Lite/Ghosts.Client.Lite.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghosts.Client.Lite", "src\Ghosts.Client.Lite.csproj", "{2457881B-2005-4694-BDAA-A8E893D2CF7D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2457881B-2005-4694-BDAA-A8E893D2CF7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2457881B-2005-4694-BDAA-A8E893D2CF7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2457881B-2005-4694-BDAA-A8E893D2CF7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2457881B-2005-4694-BDAA-A8E893D2CF7D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Ghosts.Client.Lite/README.md b/src/Ghosts.Client.Lite/README.md new file mode 100644 index 00000000..69f81941 --- /dev/null +++ b/src/Ghosts.Client.Lite/README.md @@ -0,0 +1,41 @@ +# GHOSTS LITE + +## Background + +### What is this? +GHOSTS LITE is a streamlined version of the GHOSTS framework designed to simulate realistic +network without the expense of running actual applications. It generates web browsing traffic +and file creation programatically and is configured to be similar to real-user actions. + +### Why did we build this? +We built ghosts-lite to provide a more efficient tool for simulating network activity. By +avoiding the need to launch applications like web browsers or office suites, ghosts-lite is +able to reduce the resource consumption and complexity of the simulation, resulting in saving +CPU, storage space, and other memory resources. + +### What were the goals? + +- **Efficiency:** Reduce the resources required to simualte user activity on a network. +- **Simplicity:** Simplify installation and configuration compared to the full GHOSTS framework +- **Realism:** Maintain the ability to generate realistic network activity. + +## Installation + +### Prerequisites +- Ensure you have the .NET framework installed on your system. + +### Download and install ghosts-lite + +1. **Download the latest release** from *Insert link to repository once moved*. +2. **Extract the downloaded archive** to a directory of your choice. +3. **Open terminal** and navigate to the directory where you extracted ghosts-lite + +## Troubleshooting + +- Ensure the .NET framework is installed with version 8.0 or later. +- If you're experiencing any problems please submit an issue or start a discussion. + +## License + +[DISTRIBUTION STATEMENT A] This material has been approved for public release and unlimited distribution. +Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. diff --git a/src/Ghosts.Client.Lite/lib/ghosts.domain.dll b/src/Ghosts.Client.Lite/lib/ghosts.domain.dll new file mode 100644 index 0000000000000000000000000000000000000000..dc2c5eae374f102ca158b5d35fd98c10aee6e9de GIT binary patch literal 76288 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 literal 0 HcmV?d00001 diff --git a/src/Ghosts.Client.Lite/scripts/build.sh b/src/Ghosts.Client.Lite/scripts/build.sh new file mode 100755 index 00000000..9f962c81 --- /dev/null +++ b/src/Ghosts.Client.Lite/scripts/build.sh @@ -0,0 +1,23 @@ +# Navigate to the root directory of the project +cd ../ + +# Build the project assuming the dotnet version is already installed +dotnet publish -c Release -r win-x64 + +# Navigate to the directory where ghosts-lite is located +cd src/bin/Release/net8.0 + +# Remove unnecessary files +rm win-x64/*pdb +rm win-x64/Ghosts.Client.Lite.deps.json +rm -rf win-x64/publish + +# Remove the existing ghosts-lite directory and zip if they exist +rm -rf ghosts-lite +rm ghosts-lite.zip + +# Move the win-x64 directory to ghosts-lite +mv win-x64 ghosts-lite + +# Create the zip file from within the net8.0 directory +zip -r ghosts-lite.zip ghosts-lite \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Ghosts.Client.Lite.csproj b/src/Ghosts.Client.Lite/src/Ghosts.Client.Lite.csproj new file mode 100644 index 00000000..d073b9f7 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Ghosts.Client.Lite.csproj @@ -0,0 +1,49 @@ + + + + Exe + net8.0 + Ghosts.Client.Lite + enable + enable + + GHOSTS Team for Carnegie Mellon University + Carnegie Mellon University + GHOSTS + GHOSTS NPC Orchestration Platform - please email ddupdyke[at]sei.cmu.edu with bugs/requests/other + Carnegie Mellon University 2017 + + 0.1.0.0 + 0.1.0.0 + 0.1.0.0 + 0.1.14.0 + + + + + + + + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + + + + + + ..\lib\ghosts.domain.dll + + + + diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/ApplicationDetails.cs b/src/Ghosts.Client.Lite/src/Infrastructure/ApplicationDetails.cs new file mode 100644 index 00000000..3b7c71ba --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/ApplicationDetails.cs @@ -0,0 +1,191 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using NLog; + +namespace Ghosts.Client.Lite.Infrastructure +{ + public static class ApplicationDetails + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + public static string Header => +@" ('-. .-. .-') .-') _ .-') + ( OO ) / ( OO ). ( OO) ) ( OO ). + ,----. ,--. ,--. .-'),-----. (_)---\_)/ '._ (_)---\_) + ' .-./-') | | | |( OO' .-. '/ _ | |'--...__)/ _ | + | |_( O- )| .| |/ | | | |\ :` `. '--. .--'\ :` `. + | | .--, \| |\_) | |\| | '..`''.) | | '..`''.) +(| | '. (_/| .-. | \ | | | |.-._) \ | | .-._) \ + | '--' | | | | | `' '-' '\ / | | \ / + `------' `--' `--' `-----' `-----' `--' `-----' + +"; + + /// + /// Returns current GHOSTS exe name + /// + public static string Name => Assembly.GetEntryAssembly()?.GetName().Name; + + /// + /// Returns current GHOSTS exe version + /// + public static string Version => Assembly.GetEntryAssembly()?.GetName().Version.ToString().ToUpper(); + + public static string VersionFile + { + get + { + var fileName = Assembly.GetEntryAssembly()?.Location; + return fileName != null ? FileVersionInfo.GetVersionInfo(fileName).FileVersion : ""; + } + } + + /// + /// Returns installed exe path, for commands like c:\exercise\ghosts\ghosts.exe to work properly + /// + public static string InstalledPath + { + get + { + try + { + return Clean(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.CodeBase)); + } + catch + { + return Assembly.GetEntryAssembly()?.Location; + } + } + } + + public static string GetPath(string loc) + { + return Path.GetFullPath(Path.Combine(InstalledPath, loc)); + } + + public static bool IsLinux() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + } + + // ReSharper disable once InconsistentNaming + public static bool IsOSX() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + } + + private static string Clean(string x) + { + //linux path is file:/users + //windows path is file:/z: + //ugh + var fileFormat = "file:\\"; + if (IsLinux() || IsOSX()) fileFormat = "file:"; + + if (x.Contains(fileFormat)) x = x.Substring(x.IndexOf(fileFormat, StringComparison.InvariantCultureIgnoreCase) + fileFormat.Length); + + x = x.Replace(Convert.ToChar(@"\"), Path.DirectorySeparatorChar); + x = x.Replace(Convert.ToChar(@"/"), Path.DirectorySeparatorChar); + + return x; + } + + /// + /// Paths to all the client configuration files. Config files are copyable from one instance to another. + /// + public static class ConfigurationFiles + { + public static string Path => InstalledPath + $"{System.IO.Path.DirectorySeparatorChar}config{System.IO.Path.DirectorySeparatorChar}"; + + public static string Application => Clean(Path + "application.json"); + public static string Health => Clean(Path + "health.json"); + public static string Timeline => Clean(Path + "timeline.json"); + + public static string DenyList => Clean(Path + "denylist.txt"); + public static string EmailsFooter => Clean(Path + "emails-footer.txt"); + + public static string EmailContent(string raw) => Determine(raw, "email-content.csv"); + public static string EmailReply(string raw) => Determine(raw, "email-reply.csv"); + public static string EmailDomain(string raw) => Determine(raw, "emails-domain.json"); + public static string EmailOutside(string raw) => Determine(raw, "emails-outside.json"); + public static string Dictionary(string raw) => Determine(raw, "dictionary.json"); + public static string FileNames(string raw) => Determine(raw, "filenames.txt"); + + public static string ChatMessages(string raw) => Determine(raw, "blog-reply.csv"); //lazy, use blog-reply for now + public static string LastNames(string raw) => Determine(raw, "last_names.txt"); + public static string FirstNames(string raw) => Determine(raw, "first_names.txt"); + public static string EmailTargets(string raw) => Determine(raw, "email_targets.txt"); + + //be lazy and use the Blog content as generic post content as default + public static string GenericPostContent(string raw) => Determine(raw, "blog-content.csv"); + public static string BlogContent(string raw) => Determine(raw, "blog-content.csv"); + + public static string BlogReply(string raw) => Determine(raw, "blog-reply.csv"); + + private static string Determine(string raw, string defaultValue) + { + return !string.IsNullOrEmpty(raw) ? raw : Clean(Path + defaultValue); + } + } + + public static class UserAgents + { + public static string Path => InstalledPath + + $"{System.IO.Path.DirectorySeparatorChar}config{System.IO.Path.DirectorySeparatorChar}user-agents{System.IO.Path.DirectorySeparatorChar}"; + } + + /// + /// Instance files are PER CLIENT and are kept separate, so that they are NOT accidentally copied from one host to another + /// (TL;DR - never copy instance folder) + /// + public static class InstanceFiles + { + public static string Path => InstalledPath + $"{System.IO.Path.DirectorySeparatorChar}instance{System.IO.Path.DirectorySeparatorChar}"; + public static string Id => Clean(Path + "id.json"); + public static string SurveyResults => Clean(Path + "survey-results.json"); + public static string FilesCreated => Clean(Path + "files-created.log"); + public static string Trackables => Clean(Path + "trackables.json"); + } + + public static class InstanceDirectories + { + public static string Path => InstanceFiles.Path; + + public static string TimelineIn => + Clean(Path + $"timeline{System.IO.Path.DirectorySeparatorChar}in{System.IO.Path.DirectorySeparatorChar}"); + + public static string TimelineOut => + Clean(Path + $"timeline{System.IO.Path.DirectorySeparatorChar}out{System.IO.Path.DirectorySeparatorChar}"); + } + + /// + /// Log files contain both normal debug and exception logging, + /// but also the client activity logs that are sent to the API server periodically + /// + public static class LogFiles + { + public static string Path => InstalledPath + $"{System.IO.Path.DirectorySeparatorChar}logs{System.IO.Path.DirectorySeparatorChar}"; + + public static string ClientUpdates => Clean(Path + "clientupdates.log"); + } + + public class ConfigurationUrls + { + public ConfigurationUrls(string rootUrl) + { + this._root = rootUrl; + } + + private string _root; + public string Id => $"{this._root}/clientid"; + public string Timeline => $"{this._root}/clienttimeline"; + public string Results => $"{this._root}/clientresults"; + public string Updates => $"{this._root}/clientupdates"; + public string Survey => $"{this._root}/clientsurvey"; + public string Socket => $"{this._root.Replace("/api","")}/clientHub"; + } + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Comms/CheckId.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/CheckId.cs new file mode 100644 index 00000000..80929958 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/CheckId.cs @@ -0,0 +1,152 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Net; +using Ghosts.Domain; +using NLog; + +namespace Ghosts.Client.Lite.Infrastructure.Comms +{ + /// + /// The client ID is used in the header to save having to send hostname/user/fqdn/etc. information with every request + /// + public class CheckId + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + /// + /// The actual path to the client id file, specified in application config + /// + public string IdFile = ApplicationDetails.InstanceFiles.Id; + + private DateTime _lastChecked = DateTime.Now; + private string _id = string.Empty; + + public CheckId() + { + _log.Trace($"CheckId instantiated with ID: {Id}"); + } + + /// + /// Gets the agent's current id from local instance, and if it does not exist, gets an id from the server and saves it locally + /// + public string Id + { + get + { + if (!string.IsNullOrEmpty(_id)) + return _id; + + try + { + if (!File.Exists(IdFile)) + { + if (DateTime.Now > _lastChecked.AddMinutes(5)) + { + _log.Error("Skipping Check for ID from server, too many requests in a short amount of time..."); + return string.Empty; + } + + _lastChecked = DateTime.Now; + return Run(); + } + Id = File.ReadAllText(IdFile); + return _id; + } + catch + { + _log.Error("No ID file"); + return string.Empty; + } + } + set => _id = value; + } + + /// + /// API call to get client ID (probably based on hostname, but configurable) and saves it locally + /// + /// + private string Run() + { + // ignore all certs + ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + var s = string.Empty; + + if (!Program.Configuration.Id.IsEnabled) + { + return s; + } + + var machine = new ResultMachine(); + + try + { + //call home + using (var client = WebClientBuilder.Build(machine)) + { + try + { + using (var reader = + new StreamReader(client.OpenRead(Program.ConfigurationUrls.Id) ?? throw new InvalidOperationException("CheckID client is null"))) + { + s = reader.ReadToEnd(); + _log.Debug("ID Received"); + } + } + catch (WebException wex) + { + if (wex.Message.StartsWith("The remote name could not be resolved:")) + { + _log.Debug($"API not reachable: {wex.Message}"); + } + else if (((HttpWebResponse)wex.Response).StatusCode == HttpStatusCode.NotFound) + { + _log.Debug($"No ID returned! {wex.Message}"); + } + } + catch (Exception e) + { + _log.Error($"General comms exception: {e.Message}"); + } + } + } + catch (Exception e) + { + _log.Error($"Cannot connect to API: {e.Message}"); + return string.Empty; + } + + s = s.Replace("\"", ""); + + if (!Directory.Exists(ApplicationDetails.InstanceFiles.Path)) + { + Directory.CreateDirectory(ApplicationDetails.InstanceFiles.Path); + } + + if (string.IsNullOrEmpty(s)) + { + return string.Empty; + } + + //save returned id + File.WriteAllText(IdFile, s); + return s; + } + + public static void WriteId(string id) + { + if (!string.IsNullOrEmpty(id)) + { + id = id.Replace("\"", ""); + + if (!Directory.Exists(ApplicationDetails.InstanceFiles.Path)) + { + Directory.CreateDirectory(ApplicationDetails.InstanceFiles.Path); + } + + //save returned id + File.WriteAllText(ApplicationDetails.InstanceFiles.Id, id); + } + } + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/BackgroundTaskQueue.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/BackgroundTaskQueue.cs new file mode 100644 index 00000000..42bfbc8a --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/BackgroundTaskQueue.cs @@ -0,0 +1,34 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Collections.Concurrent; + +namespace Ghosts.Client.Lite.Infrastructure.Comms.ClientSocket; + +public class BackgroundTaskQueue +{ + private ConcurrentQueue _workItems = new (); + private SemaphoreSlim _signal = new SemaphoreSlim(0); + + public IEnumerable GetAll() + { + return _workItems; + } + + public void Enqueue(QueueEntry workItem) + { + if (workItem == null) + { + throw new ArgumentNullException(nameof(workItem)); + } + + _workItems.Enqueue(workItem); + _signal.Release(); + } + + public async Task DequeueAsync(CancellationToken ct) + { + await _signal.WaitAsync(ct); + _workItems.TryDequeue(out var item); + return item; + } +} diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/ClientSocketConnection.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/ClientSocketConnection.cs new file mode 100644 index 00000000..d7206904 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/ClientSocketConnection.cs @@ -0,0 +1,154 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using Ghosts.Domain; +using Ghosts.Domain.Code; +using Microsoft.AspNetCore.SignalR.Client; + +namespace Ghosts.Client.Lite.Infrastructure.Comms.ClientSocket; + +public class Connection +{ + private int _attempts = 0; + private HubConnection _connection; + private readonly CancellationToken _ct = new(); + public readonly BackgroundTaskQueue Queue = new(); + private readonly ClientConfiguration.SocketsSettings _options; + + public Connection(ClientConfiguration.SocketsSettings options) + { + this._options = options; + } + + public async Task Run() + { + var url = Program.ConfigurationUrls.Socket; + Console.WriteLine($"Connecting to {url}..."); + while (_connection == null) + { + await EstablishConnection(url); + _attempts++; + } + + Console.WriteLine($"Connected to {url}"); + + // Send a message to the server + while (_connection.State == HubConnectionState.Connected) + { + _ = new Timer(_ => { + Task.Run(async () => { + await ClientHeartbeat(); + }, _ct).ContinueWith(task => { + if (task.Exception != null) + { + // Log or handle the exception + Console.WriteLine($"Exception in ClientHeartbeat: {task.Exception}"); + } + }, _ct); + }, null, TimeSpan.Zero, TimeSpan.FromSeconds(_options.Heartbeat)); + + while (true) + { + Console.WriteLine("Peeking into queue..."); + + var item = await Queue.DequeueAsync(this._ct); + if (item != null) + { + Console.WriteLine($"There was a {item.Type} in the queue: {item.Payload}"); + if (item.Type == QueueEntry.Types.Heartbeat) + await this.ClientHeartbeat(); + + if (item.Type == QueueEntry.Types.Message) + await this.ClientMessage(item.Payload.ToString()); + + if (item.Type == QueueEntry.Types.MessageSpecific) + await this.ClientMessageSpecific(item.Payload.ToString()); + } + else + { + await Task.Delay(new TimeSpan(0, 0, 10), _ct); + } + } + } + } + + async Task EstablishConnection(string url) + { + if (_connection != null && _connection.State != HubConnectionState.Disconnected) + { + await _connection.StopAsync(_ct); + } + + var machine = new ResultMachine(); + //GuestInfoVars.Load(machine); + + _connection = new HubConnectionBuilder() + .WithUrl(url, x => + { + x.Headers = WebClientBuilder.GetHeaders(machine, true); + }).WithAutomaticReconnect() + .Build(); + + Console.WriteLine($"Connection state: {_connection.State}"); + + // Define how to handle incoming messages + _connection.On("ReceiveHeartbeat", + (message) => { Console.WriteLine($"Heartbeat {message}"); }); + + _connection.On("ReceiveMessage", + (message) => { Console.WriteLine($"ALL: {message}"); }); + + _connection.On("ReceiveSpecificMessage", + (message) => { Console.WriteLine($"SPECIFIC: {message}"); }); + + _connection.On("ReceiveId", (id) => + { + Console.WriteLine($"ID: {id}"); + CheckId.WriteId(id); + }); + + _connection.On("ReceiveResults", + (message) => { Console.WriteLine($"Results: {message}"); }); + + _connection.On("ReceiveSurvey", + (message) => { Console.WriteLine($"Survey: {message}"); }); + + _connection.On("ReceiveUpdates", + (message) => { Console.WriteLine($"Updates: {message}"); }); + + _connection.On("ReceiveTimeline", + (message) => { Console.WriteLine($"Timeline: {message}"); }); + + _connection.Closed += async (error) => + { + Console.WriteLine($"Connection lost {error}. Trying to reconnect..."); + await EstablishConnection(url); // Call your reconnection method + }; + + try + { + await _connection.StartAsync(_ct); // Start the connection + } + catch (Exception ex) + { + if(_attempts > 1) + Console.WriteLine($"An error occurred at {url} while connecting: {ex.Message}"); + } + } + + private async Task ClientHeartbeat() + { + await _connection?.InvokeAsync("SendHeartbeat", $"Client heartbeat at {DateTime.UtcNow}", this._ct)!; + } + + private async Task ClientMessage(string message) + { + if(!string.IsNullOrEmpty(message)) + await _connection?.InvokeAsync("SendMessage", $"Client message: {message}", this._ct)!; + } + + private async Task ClientMessageSpecific(string message) + { + if(!string.IsNullOrEmpty(message)) + await _connection?.InvokeAsync("SendSpecificMessage", $"Client specific message: {message}", this._ct)!; + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/QueueEntry.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/QueueEntry.cs new file mode 100644 index 00000000..ab8e0e1d --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/ClientSocket/QueueEntry.cs @@ -0,0 +1,18 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +namespace Ghosts.Client.Lite.Infrastructure.Comms.ClientSocket; + +public class QueueEntry +{ + public enum Types + { + Id, + Heartbeat, + Message, + MessageSpecific, + Timeline + } + + public object Payload { get; set; } + public Types Type { get; set; } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Comms/Updates.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/Updates.cs new file mode 100644 index 00000000..b6762250 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Comms/Updates.cs @@ -0,0 +1,433 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Net; +using Ghosts.Domain; +using Ghosts.Domain.Code; +using Ghosts.Domain.Messages.MesssagesForServer; +using Newtonsoft.Json; +using NLog; + +namespace Ghosts.Client.Lite.Infrastructure.Comms +{ + /// + /// Get updates from the C2 server - could be timeline, health, etc. + /// + public static class Updates + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + /// + /// Threaded calls to C2 for updates and to post this client's results of activity + /// + public static void Run() + { + new Thread(() => + { + Thread.CurrentThread.IsBackground = true; + GetServerUpdates(); + }).Start(); + + new Thread(() => + { + Thread.CurrentThread.IsBackground = true; + PostClientResults(); + }).Start(); + } + + private static void GetServerUpdates() + { + if (!Program.Configuration.ClientUpdates.IsEnabled) + return; + + // ignore all certs + ServicePointManager.ServerCertificateValidationCallback += (_, _, _, _) => true; + + var machine = new ResultMachine(); + + //Thread.Sleep(Jitter.Basic(Program.Configuration.ClientUpdates.CycleSleep)); + + while (true) + { + try + { + var s = string.Empty; + using (var client = WebClientBuilder.Build(machine)) + { + try + { + using (var reader = + new StreamReader(client.OpenRead(Program.ConfigurationUrls.Updates))) + { + s = reader.ReadToEnd(); + _log.Debug($"{DateTime.Now} - Received new configuration"); + } + } + catch (WebException wex) + { + if (wex?.Response == null) + { + _log.Debug($"{DateTime.Now} - API Server appears to be not responding"); + } + else if (((HttpWebResponse)wex.Response).StatusCode == HttpStatusCode.NotFound) + { + _log.Debug($"{DateTime.Now} - No new configuration found"); + } + } + catch (Exception e) + { + _log.Error($"Exception in connecting to server: {e.Message}"); + } + } + + if (!string.IsNullOrEmpty(s)) + { + var update = JsonConvert.DeserializeObject(s); + + switch (update.Type) + { + case UpdateClientConfig.UpdateType.RequestForTimeline: + PostCurrentTimeline(update); + break; + case UpdateClientConfig.UpdateType.Timeline: + TimelineBuilder.SetLocalTimeline(update.Update.ToString()); + break; + case UpdateClientConfig.UpdateType.TimelinePartial: + try + { + var timeline = JsonConvert.DeserializeObject(update.Update.ToString()); + + foreach (var timelineHandler in timeline.TimeLineHandlers) + { + _log.Trace($"PartialTimeline found: {timelineHandler.HandlerType}"); + + foreach (var timelineEvent in timelineHandler.TimeLineEvents) + { + if (string.IsNullOrEmpty(timelineEvent.TrackableId)) + { + timelineEvent.TrackableId = Guid.NewGuid().ToString(); + } + } + + Orchestrator.RunCommand(timeline, timelineHandler); + } + } + catch (Exception exc) + { + _log.Debug(exc); + } + + break; + case UpdateClientConfig.UpdateType.Health: + { + var newTimeline = JsonConvert.DeserializeObject(update.Update.ToString()); + //save to local disk + using (var file = File.CreateText(ApplicationDetails.ConfigurationFiles.Health)) + { + var serializer = new JsonSerializer { Formatting = Formatting.Indented }; + serializer.Serialize(file, newTimeline); + } + + break; + } + default: + _log.Debug($"Update {update.Type} has no handler, ignoring..."); + break; + } + } + } + catch (Exception e) + { + _log.Debug("Problem polling for new configuration"); + _log.Error(e); + } + + Thread.Sleep(Jitter.Basic(Program.Configuration.ClientUpdates.CycleSleep)); + } + } + + private static void PostCurrentTimeline(UpdateClientConfig update) + { + // is the config for a specific timeline id? + var timelineId = TimelineUpdateClientConfigManager.GetConfigUpdateTimelineId(update); + + // get all timelines + var localTimelines = TimelineManager.GetLocalTimelines(); + + var timelines = localTimelines as Timeline[] ?? localTimelines.ToArray(); + if (timelineId != Guid.Empty) + { + foreach (var timeline in timelines) + { + if (timeline.Id == timelineId) + { + timelines = new List() + { + timeline + }.ToArray(); + break; + } + } + } + + ServicePointManager.ServerCertificateValidationCallback += (_, _, _, _) => true; + + string postUrl; + + try + { + postUrl = Program.ConfigurationUrls.Timeline; + } + catch + { + _log.Error("Can't get timeline posturl!"); + return; + } + + foreach (var timeline in timelines) + { + try + { + _log.Trace("posting timeline"); + + var payload = TimelineBuilder.TimelineToString(timeline); + var machine = new ResultMachine(); + + using (var client = WebClientBuilder.Build(machine)) + { + client.Headers[HttpRequestHeader.ContentType] = "application/json"; + client.UploadString(postUrl, JsonConvert.SerializeObject(payload)); + } + + _log.Trace($"{DateTime.Now} - timeline posted to server successfully"); + } + catch (Exception e) + { + _log.Debug( + $"Problem posting timeline to server from {ApplicationDetails.ConfigurationFiles.Timeline} to {postUrl}"); + _log.Error(e); + } + } + } + + private static void PostClientResults() + { + if (!Program.Configuration.ClientResults.IsEnabled) + return; + + // ignore all certs + ServicePointManager.ServerCertificateValidationCallback += (_, _, _, _) => true; + + var fileName = ApplicationDetails.LogFiles.ClientUpdates; + var postUrl = Program.ConfigurationUrls.Results; + + var machine = new ResultMachine(); + + Thread.Sleep(Jitter.Basic(Program.Configuration.ClientResults.CycleSleep)); + + while (true) + { + try + { + if (File.Exists(fileName)) + { + PostResults(fileName, machine, postUrl); + _log.Trace($"{fileName} posted successfully..."); + } + else + { + _log.Trace($"{DateTime.Now} - {fileName} not found - sleeping..."); + } + } + catch (Exception e) + { + _log.Error($"Problem posting logs to server: {e.Message}"); + } + finally + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + // look for other result files that have not been posted + try + { + var files = Directory.GetFiles( + Path.GetDirectoryName(fileName) ?? + throw new InvalidOperationException("Path declaration failed"), "*.log"); + foreach (var file in files) + { + if (!file.EndsWith("app.log") && file != fileName) + { + PostResults(file, machine, postUrl); + _log.Trace($"{fileName} posted successfully..."); + } + } + } + catch (Exception e) + { + _log.Debug($"Problem posting overflow logs from {fileName} to server {postUrl} : {e.Message}"); + } + finally + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + Thread.Sleep(Jitter.Basic(Program.Configuration.ClientResults.CycleSleep)); + } + } + + private static void PostResults(string fileName, ResultMachine machine, string postUrl) + { + var tempFile = ($"{fileName.Replace("clientupdates.log", Guid.NewGuid().ToString())}.log"); + if (fileName.EndsWith("clientupdates_not_posted.log")) + { + tempFile = ($"{fileName.Replace("clientupdates_not_posted.log", Guid.NewGuid().ToString())}.log"); + } + + var isCopied = false; + var i = 0; + while (!isCopied) + { + try + { + File.Move(fileName, tempFile); + isCopied = true; + break; + } + catch + { + if (i > 50) + { + throw; + } + + Thread.Sleep(1000); + } + + i++; + } + + Thread.Sleep(2000); + + string rawLogContents = null; + + try + { + using (var s = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var tr = new StreamReader(s)) + { + rawLogContents = tr.ReadToEnd(); + } + } + + var r = new TransferLogDump { Log = rawLogContents }; + var payload = JsonConvert.SerializeObject(r); + if (Program.Configuration.ClientResults.IsSecure) + { + payload = Crypto.EncryptStringAes(payload, machine.Name); + payload = Base64Encoder.Base64Encode(payload); + + var p = new EncryptedPayload { Payload = payload }; + + payload = JsonConvert.SerializeObject(p); + } + + using (var client = WebClientBuilder.Build(machine)) + { + client.Headers[HttpRequestHeader.ContentType] = "application/json"; + client.UploadString(postUrl, payload); + } + } + catch (Exception e) + { + _log.Trace($"Client results report failed: {e}"); + if (!string.IsNullOrEmpty(rawLogContents)) + { + try + { + //put the temp file contents back + var backupFile = + ApplicationDetails.LogFiles.ClientUpdates.Replace("clientupdates.log", + "clientupdates_not_posted.log"); + File.AppendAllText(backupFile, rawLogContents); + File.Delete(tempFile); + } + catch + { + _log.Trace($"Log post failure cleanup also failed: {e}"); + } + } + + throw; + } + + //delete the temp file we used for reading + File.Delete(tempFile); + _log.Trace($"{DateTime.Now} - {fileName} posted to server successfully"); + } + + internal static void PostSurvey() + { + // ignore all certs + ServicePointManager.ServerCertificateValidationCallback += (_, _, _, _) => true; + + string postUrl; + + try + { + postUrl = Program.ConfigurationUrls.Survey; + } + catch + { + _log.Error("Can't get survey posturl!"); + return; + } + + try + { + _log.Trace("posting survey"); + + Thread.Sleep(Jitter.Basic(100)); + + if (!File.Exists(ApplicationDetails.InstanceFiles.SurveyResults)) + return; + + var survey = + JsonConvert.DeserializeObject( + File.ReadAllText(ApplicationDetails.InstanceFiles.SurveyResults)); + + var payload = JsonConvert.SerializeObject(survey); + + var machine = new ResultMachine(); + + if (Program.Configuration.Survey.IsSecure) + { + payload = Crypto.EncryptStringAes(payload, machine.Name); + payload = Base64Encoder.Base64Encode(payload); + + var p = new EncryptedPayload { Payload = payload }; + + payload = JsonConvert.SerializeObject(p); + } + + using (var client = WebClientBuilder.Build(machine)) + { + client.Headers[HttpRequestHeader.ContentType] = "application/json"; + client.UploadString(postUrl, payload); + } + + _log.Trace($"{DateTime.Now} - survey posted to server successfully"); + + File.Delete(ApplicationDetails.InstanceFiles.SurveyResults); + } + catch (Exception e) + { + _log.Debug( + $"Problem posting logs to server from {ApplicationDetails.InstanceFiles.SurveyResults} to {Program.ConfigurationUrls.Survey}"); + _log.Error(e); + } + } + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/FileHandler.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/FileHandler.cs new file mode 100644 index 00000000..3a8f4979 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/FileHandler.cs @@ -0,0 +1,132 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Text; +using Ghosts.Client.Lite.Infrastructure.Services; +using Ghosts.Domain; +using Ghosts.Domain.Code.Helpers; +using Newtonsoft.Json; +using NLog; +using Quartz; + +namespace Ghosts.Client.Lite.Infrastructure.Handlers; + +public class FileCreatorJob : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var raw = context.MergedJobDataMap["handler"].ToString(); + if (string.IsNullOrEmpty(raw)) + return; + var handler = JsonConvert.DeserializeObject(raw); + if (handler == null) + return; + + await FileHandler.Run(handler); + } +} + +public class FileHandler +{ + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + public static async Task Run(TimelineHandler handler) + { + foreach (var timelineEvent in handler.TimeLineEvents) + { + await Run(handler.HandlerType, timelineEvent); + } + } + + public static async Task Run(HandlerType handler, TimelineEvent t) + { + var sizeMap = new Dictionary + { + { "Word", 1000001 }, + { "Excel", 100001 }, + { "PowerPoint", 500001 } + }; + + var rand = RandomFilename.Generate(); + + var defaultSaveDirectory = t.CommandArgs[0].ToString(); + if (defaultSaveDirectory!.Contains("%")) + { + defaultSaveDirectory = Environment.ExpandEnvironmentVariables(defaultSaveDirectory); + } + + try + { + foreach (var key in t.CommandArgs) + { + if (key.ToString()!.StartsWith("save-array:")) + { + var savePathString = key.ToString()!.Replace("save-array:", "").Replace("'", "\""); + savePathString = savePathString.Replace("\\", "/"); // Can't deserialize Windows path + var savePaths = JsonConvert.DeserializeObject(savePathString); + defaultSaveDirectory = savePaths.PickRandom().Replace("/", "\\"); // Revert to Windows path + if (defaultSaveDirectory.Contains("%")) + { + defaultSaveDirectory = Environment.ExpandEnvironmentVariables(defaultSaveDirectory); + } + + break; + } + } + } + catch (Exception e) + { + _log.Trace($"save-array exception: {e}"); + } + + defaultSaveDirectory = ApplicationDetails.GetPath(defaultSaveDirectory); + + if (!Directory.Exists(defaultSaveDirectory)) + { + Directory.CreateDirectory(defaultSaveDirectory); + } + + var ext = handler switch + { + HandlerType.Excel => "xlsx", + HandlerType.PowerPoint => "pptx", + _ => "docx" + }; + + var path = $"{defaultSaveDirectory}\\{rand}.{ext}"; + + try + { + await using (var fs = File.Create(path)) + { + _log.Trace(File.Exists(path)); + var bitLength = new Random().Next(1000, sizeMap[handler.ToString()]); + var info = new UTF8Encoding(true).GetBytes(GenerateBits(bitLength)); + await fs.WriteAsync(info, 0, info.Length); + } + + // Report on file creation success + LogWriter.Timeline(new TimeLineRecord + { + Command = t.Command, + CommandArg = path, + Handler = handler.ToString() + }); + } + catch (Exception ex) + { + _log.Error(ex); + } + } + + private static string GenerateBits(int length) + { + var rand = new Random(); + var sb = new StringBuilder(length); + for (var i = 0; i < length; i++) + { + sb.Append(rand.Next(2)); + } + + return sb.ToString(); + } +} diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/HttpHandler.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/HttpHandler.cs new file mode 100644 index 00000000..7092a9e9 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/HttpHandler.cs @@ -0,0 +1,186 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.IO.Compression; +using Ghosts.Client.Lite.Infrastructure.Services; +using Ghosts.Domain; +using Ghosts.Domain.Code.Helpers; +using HtmlAgilityPack; +using Newtonsoft.Json; +using NLog; +using Quartz; + +namespace Ghosts.Client.Lite.Infrastructure.Handlers +{ + public class WebBrowsingJob : IJob + { + public async Task Execute(IJobExecutionContext context) + { + var raw = context.MergedJobDataMap["handler"].ToString(); + if (string.IsNullOrEmpty(raw)) + return; + var handler = JsonConvert.DeserializeObject(raw); + if (handler == null) + return; + var httpHandler = new HttpHandler(); + + await httpHandler.Run(handler); + } + } + + public class HttpHandler + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + public async Task Run(TimelineHandler handler) + { + var rand = new Random(); + foreach (var timelineEvent in handler.TimeLineEvents) + { + var list = new List(); + for (var i = 0; i < rand.Next(1, 10); i++) + { + list.Add(timelineEvent.CommandArgs.PickRandom()); + } + + foreach (var site in list) + { + await Run(handler.HandlerType, timelineEvent, site.ToString()); + } + } + } + + public async Task Run(HandlerType handler, TimelineEvent t, string? url) + { + if (string.IsNullOrEmpty(url)) + { + _log.Trace("No url provided, returning..."); + return; + } + + try + { + var client = CreateHttpClient(handler, url); + var statusCode = 200; + var htmlContent = string.Empty; + try + { + var response = await client.GetAsync(client.BaseAddress); + htmlContent = await ReadContentAsync(response.Content); + _log.Debug($"Http request to {client.BaseAddress} is {response.StatusCode}"); + } + catch (HttpRequestException e) + { + _log.Debug($"Http request to {client.BaseAddress} failed with {e}"); + statusCode = e.StatusCode.HasValue ? (int)e.StatusCode.Value : -8; + } + catch (Exception ex) + { + _log.Debug($"Http request to {client.BaseAddress} failed with {ex}"); + statusCode = -9; + } + + LogWriter.Timeline(new TimeLineRecord + { + Command = t.Command, + CommandArg = url, + Handler = handler.ToString(), + Result = statusCode.ToString() + }); + + if (statusCode != 200) + return; + + var htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(htmlContent); + + await ProcessLinks(htmlDocument, handler, url, "css", "//head/link", "href"); + await ProcessLinks(htmlDocument, handler, url, "js", "//script", "src"); + await ProcessLinks(htmlDocument, handler, url, "img", "//img", "src"); + } + catch (Exception e) + { + _log.Error(e); + } + } + + private HttpClient CreateHttpClient(HandlerType handler, string url) + { + var clientHandler = new HttpClientHandler + { + UseCookies = true, + AllowAutoRedirect = true + }; + var client = new HttpClient(clientHandler) + { + BaseAddress = new Uri(url) + }; + + client.DefaultRequestHeaders.Clear(); + + switch (handler) + { + case HandlerType.BrowserChrome: + client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"); + client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 KHTML Chrome/97.0.4692.99 Safari/537.36"); + client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.9"); + client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); + client.DefaultRequestHeaders.Add("Connection", "keep-alive"); + break; + case HandlerType.BrowserFirefox: + client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); + client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0"); + client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.5"); + client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); + client.DefaultRequestHeaders.Add("Connection", "keep-alive"); + break; + } + + return client; + } + + private async Task ReadContentAsync(HttpContent content) + { + var encoding = content.Headers.ContentEncoding; + var contentStream = await content.ReadAsStreamAsync(); + + if (encoding.Contains("gzip")) + { + await using var decompressedStream = new GZipStream(contentStream, CompressionMode.Decompress); + using var reader = new StreamReader(decompressedStream); + return await reader.ReadToEndAsync(); + } + else if (encoding.Contains("deflate")) + { + await using var decompressedStream = new DeflateStream(contentStream, CompressionMode.Decompress); + using var reader = new StreamReader(decompressedStream); + return await reader.ReadToEndAsync(); + } + else + { + using var reader = new StreamReader(contentStream); + return await reader.ReadToEndAsync(); + } + } + + private async Task ProcessLinks(HtmlDocument document, HandlerType handler, string baseUrl, string logPrefix, string xpath, string attribute) + { + var nodes = document.DocumentNode.SelectNodes(xpath); + if (nodes == null) + return; + + var links = nodes.Select(node => node.GetAttributeValue(attribute, "")) + .Where(href => !string.IsNullOrEmpty(href)) + .ToList(); + + foreach (var link in links) + { + if (!baseUrl.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) + continue; + + var client = CreateHttpClient(handler, baseUrl); + var response = await client.GetAsync(link); + _log.Trace($"Request to {client.BaseAddress}{logPrefix} : {response.StatusCode}"); + } + } + } +} diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/MemoryCleanUpHandler.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/MemoryCleanUpHandler.cs new file mode 100644 index 00000000..0af93608 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Handlers/MemoryCleanUpHandler.cs @@ -0,0 +1,15 @@ +using Quartz; + +namespace Ghosts.Client.Lite.Infrastructure.Handlers; + +public class MemoryCleanupJob : IJob +{ + public Task Execute(IJobExecutionContext context) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + return Task.CompletedTask; + } +} diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Orchestrator.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Orchestrator.cs new file mode 100644 index 00000000..4ee02d61 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Orchestrator.cs @@ -0,0 +1,48 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using Ghosts.Client.Lite.Infrastructure.Handlers; +using Ghosts.Domain; +using NLog; + +namespace Ghosts.Client.Lite.Infrastructure; + +public class Orchestrator +{ + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + public static async Task RunCommand(Timeline timeline, TimelineHandler handler) + { + await ThreadLaunch(timeline, handler); + } + + private static async Task ThreadLaunch(Timeline timeline, TimelineHandler handler) + { + var http = new HttpHandler(); + // foreach thing in the timeline determine which handler you need + switch (handler.HandlerType) + { + case HandlerType.BrowserChrome: + case HandlerType.BrowserFirefox: + foreach (var timelineEvent in handler.TimeLineEvents) + { + foreach (var site in timelineEvent.CommandArgs) + { + await http.Run(handler.HandlerType, timelineEvent, site.ToString()); + } + } + break; + case HandlerType.Excel: + case HandlerType.PowerPoint: + case HandlerType.Word: + foreach (var timelineEvent in handler.TimeLineEvents) + { + foreach (var file in timelineEvent.CommandArgs) + { + await FileHandler.Run(handler.HandlerType, timelineEvent); + } + } + + break; + } + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/RandomFilename.cs b/src/Ghosts.Client.Lite/src/Infrastructure/RandomFilename.cs new file mode 100644 index 00000000..a1270645 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/RandomFilename.cs @@ -0,0 +1,64 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using Ghosts.Domain.Code.Helpers; + +namespace Ghosts.Client.Lite.Infrastructure; + +public static class RandomFilename +{ + private static readonly Random _random = new (); + + public static string Generate() + { + // load config file otherwise use hardcoded list for older clients + var list = new List + { + "report", + "receipts", + "Results", + "Final $x$", + "draft", + "netcom", + "army", + "FY18-report", + "intel-$x$", + "Beluka", + "report-intel", + "Jalton-AR", + "inter_office_report", + "working_draft", + "report for tom", + "cpt wilson report", + "sgt farr sitrep", + "file_name", + "todo list", + "dont forget to do ths - $x$", + "RiskManagement", + "RPODirectory", + "Agenda", + "OfficeProcedures-$x$" + }; + + var fileName = list.PickRandom(); + + // add variables? + if (fileName.Contains("$x$")) + { + var rand = _random.Next(0, 4); + switch (rand) + { + case 0: + fileName = fileName.Replace("$x$", _random.Next(0, 12).ToString()); + break; + case 1: + fileName = fileName.Replace("$x$", DateTime.Now.Month.ToString()); + break; + case 2: + fileName = fileName.Replace("$x$", _random.Next(0, 30).ToString()); + break; + } + } + + return fileName; + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/Services/LogWriter.cs b/src/Ghosts.Client.Lite/src/Infrastructure/Services/LogWriter.cs new file mode 100644 index 00000000..79580ed1 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/Services/LogWriter.cs @@ -0,0 +1,24 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using Ghosts.Domain; +using Newtonsoft.Json; +using NLog; + +namespace Ghosts.Client.Lite.Infrastructure.Services; + +public static class LogWriter +{ + private static readonly Logger _timelineLog = LogManager.GetLogger("TIMELINE"); + + public static void Timeline(TimeLineRecord result) + { + var o = JsonConvert.SerializeObject(result, + Formatting.None, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }); + + _timelineLog.Info($"TIMELINE|{DateTime.UtcNow}|{o}"); + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Infrastructure/WebClientHeaders.cs b/src/Ghosts.Client.Lite/src/Infrastructure/WebClientHeaders.cs new file mode 100644 index 00000000..e232da07 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Infrastructure/WebClientHeaders.cs @@ -0,0 +1,52 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Net; +using Ghosts.Domain; +using Ghosts.Domain.Code; + +namespace Ghosts.Client.Lite.Infrastructure +{ + /// + /// Sets web request headers for updates/post of results + /// + public static class WebClientBuilder + { + public static WebClient Build(ResultMachine machine, bool useId = true) + { + var client = new WebClient(); + foreach (var header in GetHeaders(machine, useId)) + { + client.Headers.Add(header.Key, header.Value); + } + + return client; + } + + public static IDictionary GetHeaders(ResultMachine machine, bool useId = true) + { + var dict = new Dictionary(); + + dict.Add(HttpRequestHeader.UserAgent.ToString(), "Ghosts Client"); + if (!string.IsNullOrEmpty(machine.Id)) + { + dict.Add("ghosts-id", machine.Id); + } + + dict.Add("ghosts-name", machine.Name); + dict.Add("ghosts-fqdn", machine.FQDN); + dict.Add("ghosts-host", machine.Host); + dict.Add("ghosts-domain", machine.Domain); + dict.Add("ghosts-resolvedhost", machine.ResolvedHost); + dict.Add("ghosts-ip", machine.ClientIp); + + var username = machine.CurrentUsername; + if (Program.Configuration.EncodeHeaders) + username = Base64Encoder.Base64Encode(username); + + dict.Add("ghosts-user", username); + dict.Add("ghosts-version", Domain.Code.ApplicationDetails.Version); + + return dict; + } + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/Program.cs b/src/Ghosts.Client.Lite/src/Program.cs new file mode 100644 index 00000000..62633954 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/Program.cs @@ -0,0 +1,172 @@ +// Copyright 2017 Carnegie Mellon University. All Rights Reserved. See LICENSE.md file for terms. + +using System.Drawing; +using System.Net; +using System.Reflection; +using Ghosts.Client.Lite.Infrastructure.Comms; +using Ghosts.Client.Lite.Infrastructure.Comms.ClientSocket; +using Ghosts.Client.Lite.Infrastructure.Handlers; +using Ghosts.Domain; +using Ghosts.Domain.Code; +using Newtonsoft.Json; +using NLog; +using Quartz; +using Quartz.Impl; + +namespace Ghosts.Client.Lite; + +internal static class Program +{ + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + internal static ClientConfiguration Configuration { get; private set; } + internal static ApplicationDetails.ConfigurationUrls ConfigurationUrls { get; set; } + internal static BackgroundTaskQueue Queue; + public static CheckId CheckId { get; set; } + + public static async Task Main(string[] args) + { + try + { + await Run(args); + } + catch (Exception e) + { + Console.WriteLine($"Fatal exception in {ApplicationDetails.Name} {ApplicationDetails.Version}: {e}", + Color.Red); + _log.Fatal(e); + Console.ReadLine(); + } + } + + private static async Task Run(string[] args) + { + var rand = new Random(); + + // Ignore all certs + ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + // Load configuration + try + { + Configuration = ClientConfigurationLoader.Config; + ConfigurationUrls = new ApplicationDetails.ConfigurationUrls(Configuration.ApiRootUrl); + } + catch (Exception e) + { + var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var o = + $"Exec path: {path} - configuration 404: {ApplicationDetails.ConfigurationFiles.Application} - exiting. Exception: {e}"; + _log.Fatal(o); + Console.WriteLine(o, Color.Red); + Console.ReadLine(); + return; + } + + _log.Info(ApplicationDetails.Header); + _log.Info( + $"Initiating {ApplicationDetails.Name} startup - Local: {DateTime.Now.TimeOfDay} UTC: {DateTime.UtcNow.TimeOfDay}"); + + // Get default timeline from config/timeline.json + var timeline = TimelineBuilder.GetTimeline(); + + // Does this instance of the application have an Id? + CheckId = new CheckId(); + _log.Trace($"CheckID: {CheckId.Id}"); + + // Connect to ghosts API via websockets on a separate thread + _log.Trace("Sockets enabled. Connecting..."); + var c = new Connection(Configuration.Sockets); + Queue = c.Queue; + + // Start the connection in a new task + _ = Task.Run(async () => await c.Run()); + + // Connect to command server over "old style polling" as the fallback for updates and sending logs + // This is a separate thread also + _ = Task.Run(() => Updates.Run()); + + // Fall into executing activity + var schedulerFactory = new StdSchedulerFactory(); + var scheduler = await schedulerFactory.GetScheduler(); + await scheduler.Start(); + + // Schedule timeline + await ScheduleTimeline(scheduler, timeline); + + // Schedule memory cleanup job + await ScheduleMemoryCleanup(scheduler); + + await Task.Delay(-1); // run forever + } + + private static async Task ScheduleTimeline(IScheduler scheduler, Timeline timeline) + { + var rand = new Random(); + foreach (var handler in timeline.TimeLineHandlers) + { + switch (handler.HandlerType) + { + case HandlerType.BrowserChrome: + case HandlerType.BrowserFirefox: + var job = JobBuilder.Create() + .UsingJobData("handler", JsonConvert.SerializeObject(handler)) + .Build(); + foreach (var timelineEvent in handler.TimeLineEvents) + { + // Trigger the job to run after a random short delay + var trigger = TriggerBuilder.Create() + .StartNow() // Start immediately + .WithSimpleSchedule(x => x + .WithIntervalInSeconds(GetJitteredDelay(timelineEvent.DelayAfterActual, rand)) + .RepeatForever()) + .Build(); + // Schedule the job with the trigger + await scheduler.ScheduleJob(job, trigger); + } + break; + + case HandlerType.Excel: + case HandlerType.PowerPoint: + case HandlerType.Word: + job = JobBuilder.Create() + .UsingJobData("handler", JsonConvert.SerializeObject(handler)) + .Build(); + foreach (var timelineEvent in handler.TimeLineEvents) + { + // Trigger the job to run after a random short delay + var trigger = TriggerBuilder.Create() + .StartNow() // Start immediately + .WithSimpleSchedule(x => x + .WithIntervalInSeconds(GetJitteredDelay(timelineEvent.DelayAfterActual, rand)) + .RepeatForever()) + .Build(); + // Schedule the job with the trigger + await scheduler.ScheduleJob(job, trigger); + } + break; + } + } + } + + private static async Task ScheduleMemoryCleanup(IScheduler scheduler) + { + var job = JobBuilder.Create() + .Build(); + + // Schedule the job to run every hour + var trigger = TriggerBuilder.Create() + .StartNow() + .WithSimpleSchedule(x => x + .WithIntervalInHours(1) + .RepeatForever()) + .Build(); + + await scheduler.ScheduleJob(job, trigger); + } + + private static int GetJitteredDelay(int baseDelay, Random rand) + { + var jitterFactor = 1 + (rand.NextDouble() * 0.2 - 0.1); // Random value between -0.1 and +0.1 + return (int)(baseDelay * jitterFactor / 10); // seconds to milliseconds, since ghosts timelines are in ms + } +} diff --git a/src/Ghosts.Client.Lite/src/config/application.json b/src/Ghosts.Client.Lite/src/config/application.json new file mode 100644 index 00000000..4d150b17 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/config/application.json @@ -0,0 +1,25 @@ +{ + "ApiRootUrl": "http://telemetry.exercise.mil/api", + "Sockets": { + "IsEnabled": true, + "Heartbeat": 50000 + }, + "Id": { + "IsEnabled": true, + "Format": "guestlocal", + "FormatKey": "guestinfo.id", + "FormatValue": "$formatkeyvalue$-$machinename$", + "VMWareToolsLocation": "C:\\progra~1\\VMware\\VMware Tools\\vmtoolsd.exe" + }, + "AllowMultipleInstances": false, + "EncodeHeaders": true, + "ClientResults": { + "IsEnabled": true, + "IsSecure": false, + "CycleSleep": 300000 + }, + "ClientUpdates": { + "IsEnabled": true, + "CycleSleep": 300000 + } +} \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/config/timeline.json b/src/Ghosts.Client.Lite/src/config/timeline.json new file mode 100644 index 00000000..a38a6d42 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/config/timeline.json @@ -0,0 +1,616 @@ +{ + "Status": "Run", + "TimeLineHandlers": [ + { + "HandlerType": "Word", + "Initial": "", + "UtcTimeOn": "00:00:00", + "UtcTimeOff": "24:00:00", + "HandlerArgs": { + "workingset": { + "max": 20, + "max-age-in-hours": 72 + } + }, + "Loop": "True", + "TimeLineEvents": [ + { + "Command": "create", + "CommandArgs": [ "%homedrive%%homepath%\\Documents", "pdf", "pdf-vary-filenames", "save-array:['c:\\tmp','c:\\tmp\\path2','c:\\tmp\\path3']" ], + "DelayAfter": 9000, + "DelayBefore": 0 + } + ] + }, + { + "HandlerType": "Excel", + "Initial": "", + "UtcTimeOn": "00:00:00", + "UtcTimeOff": "24:00:00", + "Loop": "True", + "TimeLineEvents": [ + { + "Command": "create", + "CommandArgs": [ "%homedrive%%homepath%\\Documents", "pdf", "pdf-vary-filenames", "save-array:['c:\\tmp','c:\\tmp\\path2','c:\\tmp\\path3']" ], + "DelayAfter": 9000, + "DelayBefore": 0 + } + ] + }, + { + "HandlerType": "PowerPoint", + "Initial": "", + "UtcTimeOn": "00:00:00", + "UtcTimeOff": "24:00:00", + "Loop": "True", + "TimeLineEvents": [ + { + "Command": "create", + "CommandArgs": [ "%homedrive%%homepath%\\Documents", "pdf", "pdf-vary-filenames", "save-array:['c:\\tmp','c:\\tmp\\path2','c:\\tmp\\path3']" ], + "DelayAfter": 9000, + "DelayBefore": 0 + } + ] + }, + { + "HandlerType": "BrowserFirefox", + "HandlerArgs": { + "isheadless": "false", + "blockimages": "false", + "blockstyles": "false", + "blockflash": "false", + "blockscripts": "false", + "stickiness": "65", + "stickiness-depth-min": "3", + "stickiness-depth-max": "15", + "incognito": "false", + "javascript-enable": "true", + "visited-remember": "10", + "actions-before-restart": 100, + "command-line-args": [ "--ignore-certificate-errors" ], + "url-replace": [ + { "verb": [ "order", "enable", "engage" ] }, + { "group": [ "operations", "logistics", "medical" ] }, + { "org": [ "army", "command", "brigade", "battalion" ] }, + { "type": [ "document", "doc", "files", "vault", "filevault" ] } + ] + }, + "Initial": "about:blank", + "UtcTimeOn": "00:00:00", + "UtcTimeOff": "24:00:00", + "UtcTimeBlocks": [ "00:00:00", "23:59:00" ], + "Loop": "True", + "TimeLineEvents": [ + { + "Command": "random", + "CommandArgs": [ + "http://craigslist.org/{org}/{group}/{uuid}/{verb}/{type}/{n}?{c}={now}", + "http://www.ceoexpress.com", + "http://wikipedia.org", + "http://ebay.com", + "http://craigslist.org", + "http://reddit.com", + "http://instagram.com", + "http://imdb.com", + "http://huffingtonpost.com", + "http://nytimes.com", + "http://msn.com", + "http://buzzfeed.com", + "http://homedepot.com", + "http://target.com", + "http://bestbuy.com", + "http://about.com", + "http://intuit.com", + "http://cnet.com", + "http://verizonwireless.com", + "http://reference.com", + "http://fedex.com", + "http://ancestry.com", + "http://swagbucks.com", + "http://comcast.com", + "http://hp.com", + "http://steamcommunity.com", + "http://wow.com", + "http://wikimedia.org", + "http://io9.com", + "http://dailymotion.com", + "http://nbcsports.com", + "http://walgreens.com", + "http://match.com", + "http://photobucket.com", + "http://barnesandnoble.com", + "http://bhphotovideo.com", + "http://nhl.com", + "http://archive.org", + "http://merriam-webster.com", + "http://booking.com", + "http://bodybuilding.com", + "http://sourceforge.net", + "http://evite.com", + "http://careerbuilder.com", + "http://shareasale.com", + "http://www.abs-cbnnews.com", + "http://roblox.com", + "http://lotterypost.com", + "http://www.dfas.mil", + "http://www.dia.mil", + "http://www.careerjournal.com", + "http://www.jacksonhewitt.com", + "http://factcheck.org", + "http://audacityteam.org", + "http://7-zip.org", + "http://amazon.com", + "http://cnn.com", + "http://espn.go.com", + "http://apple.com", + "http://aol.com", + "http://comcast.net", + "http://www.thelayoff.com", + "http://groupon.com", + "http://stackoverflow.com", + "http://usatoday.com", + "http://slickdeals.net", + "http://businessinsider.com", + "http://dailymail.co.uk", + "http://lowes.com", + "http://bleacherreport.com", + "http://macys.com", + "http://webmd.com", + "http://trulia.com", + "http://answers.com", + "http://newegg.com", + "http://deviantart.com", + "http://nih.gov", + "http://wsj.com", + "http://theguardian.com", + "http://goodreads.com", + "http://force.com", + "http://wunderground.com", + "http://ca.gov", + "http://overstock.com", + "http://nbcnews.com", + "http://drudgereport.com", + "http://tmz.com", + "http://okcupid.com", + "http://bloomberg.com", + "http://accuweather.com", + "http://infusionsoft.com", + "http://meetup.com", + "http://woot.com", + "http://abcnews.go.com", + "http://godaddy.com", + "http://mashable.com", + "http://people.com", + "http://xfinity.com", + "http://mailchimp.com", + "http://united.com", + "http://allrecipes.com", + "http://samsclub.com", + "http://4dsply.com", + "http://indiatimes.com", + "http://rei.com", + "http://cars.com", + "http://myway.com", + "http://toysrus.com", + "http://stumbleupon.com", + "http://greatergood.com", + "http://thefreedictionary.com", + "http://orbitz.com", + "http://java.com", + "http://www.move.mil", + "http://ign.com", + "http://lifehacker.com", + "http://retailmenot.com", + "http://nordstrom.com", + "http://sears.com", + "http://slate.com", + "http://ticketmaster.com", + "http://nba.com", + "http://houzz.com", + "http://dell.com", + "http://disney.com", + "http://wayfair.com", + "http://ehow.com", + "http://bridaltune.com", + "http://duckduckgo.com", + "http://consumerreports.org", + "http://zergnet.com", + "http://directv.com", + "http://edmunds.com", + "http://fiverr.com", + "http://gamespot.com", + "http://icims.com", + "http://seekingalpha.com", + "http://androidcentral.com", + "http://citrix.com", + "http://nbc.com", + "http://politico.com", + "http://timeanddate.com", + "http://cmu.edu", + "http://cert.org", + "http://sei.cmu.edu", + "http://www.army.mil", + "http://www.navy.mil", + "http://www.af.mil", + "http://www.marines.mil", + "http://tricare.mil", + "http://www.dla.mil", + "http://www.darpa.mil", + "http://militaryonesource.mil", + "http://www.dma.mil", + "http://www.dss.mil", + "http://cac.mil", + "http://www.jcs.mil", + "http://www.africom.mil", + "http://mda.mil", + "http://www.dcma.mil", + "http://dsca.mil", + "http://health.mil", + "http://www.soc.mil", + "http://www.jsf.mil", + "http://www.norad.mil", + "http://www.dcaa.mil", + "http://ssa.gov", + "http://www.1040.com", + "http://www.adexa.com", + "http://www.ithound.com", + "http://mobile.thehill.com", + "http://modeling.about.com", + "http://modeltrains.about.com", + "http://money.cnn.com", + "http://ccleaner.com", + "http://qbittorrent.org", + "http://gimp.org", + "http://spotify.com", + "http://weebly.com", + "http://ups.com", + "http://npr.org", + "http://adobe.com", + "http://likes.com", + "http://coupons.com", + "http://mint.com", + "http://cracked.com", + "http://yellowpages.com", + "http://deadspin.com", + "http://cbs.com", + "http://hootsuite.com", + "http://fitbit.com", + "http://ew.com", + "http://legacy.com", + "http://marketwatch.com", + "http://audible.com", + "http://qvc.com", + "http://faithtap.com", + "http://hgtv.com", + "http://wix.com", + "http://today.com", + "http://littlethings.com", + "http://kbb.com", + "http://wordpress.org", + "http://addthis.com", + "http://kotaku.com", + "http://ebates.com", + "http://slideshare.net", + "http://popsugar.com", + "http://jezebel.com", + "http://moba-stream.com", + "http://chicagotribune.com", + "http://reuters.com", + "http://fool.com", + "http://tvguide.com", + "http://distractify.com", + "http://usnews.com", + "http://topix.com", + "http://bizjournals.com", + "http://city-data.com", + "http://shutterstock.com", + "http://vrbo.com", + "http://mozilla.org", + "http://officedepot.com", + "http://jalopnik.com", + "http://hollywoodreporter.com", + "http://xda-developers.com", + "http://macrumors.com", + "http://ny.gov", + "http://cafemom.com", + "http://worldstarhiphop.com", + "http://conservativetribune.com", + "http://kmart.com", + "http://speedtest.net", + "http://stanford.edu", + "http://viralnova.com", + "http://investopedia.com", + "http://tigerdirect.com", + "http://foxsports.com", + "http://4chan.org", + "http://trello.com", + "http://fatwallet.com", + "http://arstechnica.com", + "http://lwn.net", + "http://www.nationalguard.mil", + "http://google.com", + "http://wireshark.org", + "http://www.yss.com", + "http://zenhabits.net", + "http://zea.wikipedia.org", + "http://za.wikipedia.org", + "http://youreconomy.org", + "http://yucatan.backpage.mx", + "http://youngstown.backpage.com", + "http://youngadults.about.com", + "http://yahoo.match.com", + "http://xfinity.cnbc.com", + "http://www.cdc.gov", + "http://www.zergnet.com", + "http://www.ziffdavis.com", + "http://www.zepol.com", + "http://www.yourmiddleeast.com", + "http://www.yummynames.com", + "http://www.yss.com", + "http://www.yourversion.com", + "http://www.yhd.com", + "http://www.worldairlineawards.com", + "http://www.womenshealthmag.com", + "http://www.wolframalpha.com", + "http://www.wolfram.com", + "http://www.witi.com", + "http://www.wiseradar.com", + "http://www.winzip.com", + "http://www.wine.com", + "http://www.wimp.com", + "http://www.vos.noaa.gov", + "http://www.visitorville.com", + "http://www.vikings.com", + "http://www.vice.com", + "http://www.va.gov", + "http://www.usxpress.com", + "http://www.usairways.com", + "http://www.twilert.com", + "http://www.tvguide.com", + "http://www.tribpub.com", + "http://www.traveltune.com", + "http://www.travelpod.com", + "http://www.trackurstatus.com", + "http://www.toptensocialmedia.com", + "http://www.toptenreviews.com", + "http://www.surfwax.com", + "http://www.sublimetext.com", + "http://www.stratcom.mil", + "http://www.stopfakes.gov", + "http://www.stanford.edu", + "http://www.stanfordchildrens.org", + "http://www.spotify.com", + "http://www.spj.org", + "http://www.score.org", + "http://www.science.gov", + "http://www.rollingstone.com", + "http://www.rferl.org", + "http://www.register.com", + "http://www.redskins.com", + "http://www.reddit.com", + "http://www.ready.gov", + "http://www.qvc.de", + "http://www.psu.edu", + "http://www.procon.org", + "http://www.plus.google.com", + "http://www.planethistory.com", + "http://www.philly.com", + "http://www.periscope.tv", + "http://www.popysdiary.com", + "http://www.pearson.com", + "http://www.patriots.com", + "http://www.oyster.com", + "http://www.ookla.com", + "http://www.oemsecrets.com", + "http://www.odfl.com", + "http://www.northernlight.com", + "http://www.northamericanwhitetail.com", + "http://www.north-africa.com", + "http://www.nordeste.com", + "http://www.noradsanta.org", + "http://www.nndb.com", + "http://www.nmcrs.org", + "http://www.nilemedia.com", + "http://www.nike.com", + "http://www.nih.gov", + "http://www.nic.at", + "http://www.nflplayerengagement.com", + "http://www.nflplayercare.com", + "http://www.newyorker.com", + "http://www.newwebdirectory.com", + "http://www.newsy.com", + "http://www.newsweek.com", + "http://www.newstips.org", + "http://www.newsroom.aaa.com", + "http://www.newsmax.com", + "http://www.newsisfree.com", + "http://www.newcars.com", + "http://www.netbase.com", + "http://www.nbda.org", + "http://www.natsci.colostate.edu", + "http://www.naric.com", + "http://www.nannynetwork.com", + "http://www.nadaguides.com", + "http://www.myparentime.com", + "http://www.mozilla.com", + "http://www.movabletype.com", + "http://www.motherjones.com", + "http://www.mmh.com", + "http://www.miga.org", + "http://www.microsoftvirtualacademy.com", + "http://www.miamiindulge.com", + "http://www.miamidolphins.com", + "http://www.memri.org", + "http://www.meforum.org", + "http://www.meed.com", + "http://www.mediamiser.com", + "http://www.mbp.state.md.us", + "http://www.mba-online-program.com", + "http://www.mayoclinic.org", + "http://www.manta.com", + "http://www.majuredata.com", + "http://www.makeup.com", + "http://www.lyricsworld.com", + "http://www.lpaonline.org", + "http://www.yss.com", + "http://zenhabits.net", + "http://zea.wikipedia.org", + "http://za.wikipedia.org", + "http://youreconomy.org", + "http://yucatan.backpage.mx", + "http://youngstown.backpage.com", + "http://youngadults.about.com", + "http://yahoo.match.com", + "http://xfinity.cnbc.com", + "http://www.cdc.gov", + "http://www.zergnet.com", + "http://www.ziffdavis.com", + "http://www.zepol.com", + "http://www.yourmiddleeast.com", + "http://www.yummynames.com", + "http://www.yss.com", + "http://www.yourversion.com", + "http://www.yhd.com", + "http://www.worldairlineawards.com", + "http://www.womenshealthmag.com", + "http://www.wolframalpha.com", + "http://www.wolfram.com", + "http://www.witi.com", + "http://www.wiseradar.com", + "http://www.winzip.com", + "http://www.wine.com", + "http://www.wimp.com", + "http://www.vos.noaa.gov", + "http://www.visitorville.com", + "http://www.vikings.com", + "http://www.vice.com", + "http://www.va.gov", + "http://www.usxpress.com", + "http://www.usairways.com", + "http://www.twilert.com", + "http://www.tvguide.com", + "http://www.tribpub.com", + "http://www.traveltune.com", + "http://www.travelpod.com", + "http://www.trackurstatus.com", + "http://www.toptensocialmedia.com", + "http://www.toptenreviews.com", + "http://www.surfwax.com", + "http://www.sublimetext.com", + "http://www.stratcom.mil", + "http://www.stopfakes.gov", + "http://www.stanford.edu", + "http://www.stanfordchildrens.org", + "http://www.spotify.com", + "http://www.spj.org", + "http://www.score.org", + "http://www.science.gov", + "http://www.rollingstone.com", + "http://www.rferl.org", + "http://www.register.com", + "http://www.redskins.com", + "http://www.reddit.com", + "http://www.ready.gov", + "http://www.qvc.de", + "http://www.psu.edu", + "http://www.procon.org", + "http://www.plus.google.com", + "http://www.planethistory.com", + "http://www.philly.com", + "http://www.periscope.tv", + "http://www.popysdiary.com", + "http://www.pearson.com", + "http://www.patriots.com", + "http://www.oyster.com", + "http://www.ookla.com", + "http://www.oemsecrets.com", + "http://www.odfl.com", + "http://www.northernlight.com", + "http://www.northamericanwhitetail.com", + "http://www.north-africa.com", + "http://www.nordeste.com", + "http://www.noradsanta.org", + "http://www.nndb.com", + "http://www.nmcrs.org", + "http://www.nilemedia.com", + "http://www.nike.com", + "http://www.nih.gov", + "http://www.nic.at", + "http://www.nflplayerengagement.com", + "http://www.nflplayercare.com", + "http://www.newyorker.com", + "http://www.newwebdirectory.com", + "http://www.newsy.com", + "http://www.newsweek.com", + "http://www.newstips.org", + "http://www.newsroom.aaa.com", + "http://www.newsmax.com", + "http://www.newsisfree.com", + "http://www.newcars.com", + "http://www.netbase.com", + "http://www.nbda.org", + "http://www.natsci.colostate.edu", + "http://www.naric.com", + "http://www.nannynetwork.com", + "http://www.nadaguides.com", + "http://www.myparentime.com", + "http://www.mozilla.com", + "http://www.movabletype.com", + "http://www.motherjones.com", + "http://www.mmh.com", + "http://www.miga.org", + "http://www.microsoftvirtualacademy.com", + "http://www.miamiindulge.com", + "http://www.miamidolphins.com", + "http://www.memri.org", + "http://www.meforum.org", + "http://www.meed.com", + "http://www.mediamiser.com", + "http://www.mbp.state.md.us", + "http://www.mba-online-program.com", + "http://www.mayoclinic.org", + "http://www.manta.com", + "http://www.majuredata.com", + "http://www.makeup.com", + "http://www.lyricsworld.com", + "http://www.lpaonline.org", + { + "Uri": "http://httpbin.org/post", + "Category": "cat1", + "Method": "POST", + "Headers": { + "1": "a", + "2": "b" + }, + "FormValues": { + "1": "a", + "2": "b" + } + }, + { + "Uri": "http://httpbin.org/put", + "Category": "cat1", + "Method": "PUT", + "Headers": { + "1": "a", + "2": "b" + }, + "Body": "body" + }, + { + "Uri": "http://httpbin.org/delete", + "Category": "cat1", + "Method": "DELETE" + } + ], + "DelayAfter": { + "random": true, + "min": 5000, + "max": 30000 + }, + "DelayBefore": 0 + } + ] + } + ] + } \ No newline at end of file diff --git a/src/Ghosts.Client.Lite/src/nlog.config b/src/Ghosts.Client.Lite/src/nlog.config new file mode 100644 index 00000000..30b60040 --- /dev/null +++ b/src/Ghosts.Client.Lite/src/nlog.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file