summaryrefslogtreecommitdiffstats
path: root/nselib/data/psexec/nmap_service.c
blob: a6a563b281676871c316b25d42cc2bad1d0fab00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/**This is the program that's uploaded to a Windows machine when psexec is run. It acts as a Windows
 * service, since that's what Windows expects. When it is started, it's passed a list of programs to
 * run. These programs are all expected to be at the indicated path (whether they were uploaded or
 * they were always present makes no difference).
 *
 * After running the programs, the output from each of them is ciphered with a simple xor encryption
 * (the encryption key is passed as a parameter; because it crosses the wire, it isn't really a
 * security feature, more of validation/obfuscation to prevent sniffers from grabbing the output. This
 * output is placed in a temp file. When the cipher is complete, the output is moved into a new file.
 * When Nmap detects the presence of this new file, it is downloaded, then all files, temp files, and
 * the service (this program) is deleted.
 *
 * One interesting note is that executable files don't require a specific extension to be used by this
 * program. By default, at the time of this writing, Nmap appends a .txt extension to the file.
 *
 * @args argv[1]   The final filename where the ciphered output will go.
 * @args argv[2]   The temporary file where output is sent before being renamed; this is sent as a parameter
 *                 so we can delete it later (if, say, the script fails).
 * @args argv[3]   The number of programs that are going to be run.
 * @args argv[4]   Logging: a boolean value (1 to enable logging, 0 to disable).
 * @args argv[5]   An 'encryption' key for simple 'xor' encryption. This string can be as long or as short
 *                 as you want, but a longer string will be more secure (although this algorithm should
 *                 *never* really be considered secure).
 * @args Remaining There are two arguments for each program to run: a path (including arguments) and
 *                 environmental variables.
 *
 * @auther    Ron Bowes
 * @copyright Ron Bowes
 * @license   "Same as Nmap--See https://nmap.org/book/man-legal.html"
 */

#include <stdio.h>
#include <windows.h>

FILE *outfile;

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

static char *enc_key;
static int   enc_key_loc;

static void log_message(char *format, ...)
{
	static int enabled = 1;

	if(!format)
	{
		enabled = 0;
		DeleteFile("c:\\nmap-log.txt");
	}


	if(enabled)
	{
		va_list argp;
		FILE *file;

		fopen_s(&file, "c:\\nmap-log.txt", "a");

		if(file != NULL)
		{
			va_start(argp, format);
			vfprintf(file, format, argp);
			va_end(argp);
			fprintf(file, "\n");
			fclose(file);
		}
	}
}

static char cipher(char c)
{
	if(strlen(enc_key) == 0)
		return c;

	c = c ^ enc_key[enc_key_loc];
	enc_key_loc = (enc_key_loc + 1) % strlen(enc_key);

	return c;
}

static void output(int num, char *str, int length)
{
	int i;

	if(length == -1)
		length = strlen(str);

	for(i = 0; i < length; i++)
	{
		if(str[i] == '\n')
		{
			fprintf(outfile, "%c", cipher('\n'));
			fprintf(outfile, "%c", cipher('0' + (num % 10)));
		}
		else
		{
			fprintf(outfile, "%c", cipher(str[i]));
		}
	}
}

static void go(int num, char *lpAppPath, char *env, int headless, int include_stderr, char *readfile)
{
	STARTUPINFO         startupInfo;
	PROCESS_INFORMATION processInformation;
	SECURITY_ATTRIBUTES sa;
	HANDLE              stdout_read, stdout_write;
	DWORD               creation_flags;

	int bytes_read;
	char buffer[1024];

	/* Create a security attributes structure. This is required to inherit handles. */
	ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
	sa.nLength              = sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;

	if(!headless)
		sa.bInheritHandle       = TRUE;

	/* Create a pipe that'll be used for stdout and stderr. */
	if(!headless)
		CreatePipe(&stdout_read, &stdout_write, &sa, 1);

	/* Initialize the startup info struct. The most important part is setting the stdout/stderr handle to our pipe. */
	ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
	startupInfo.cb         = sizeof(STARTUPINFO);

	if(!headless)
	{
		startupInfo.dwFlags    = STARTF_USESTDHANDLES;
		startupInfo.hStdOutput = stdout_write;
		if(include_stderr)
			startupInfo.hStdError  = stdout_write;
	}

	/* Log a couple messages. */
	log_message("Attempting to load the program: %s", lpAppPath);

	/* Initialize the PROCESS_INFORMATION structure. */
	ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));

	/* To divide the output from one program to the next */
	output(num, "\n", -1);

	/* Decide on the creation flags */
	creation_flags = CREATE_NO_WINDOW;
	if(headless)
		creation_flags = DETACHED_PROCESS;

	/* Create the actual process with an overly-complicated CreateProcess function. */
	if(!CreateProcess(NULL, lpAppPath, 0, &sa, sa.bInheritHandle, CREATE_NO_WINDOW, env, 0, &startupInfo, &processInformation))
	{
		output(num, "Failed to create the process", -1);

		if(!headless)
		{
			CloseHandle(stdout_write);
			CloseHandle(stdout_read);
		}
	}
	else
	{
		log_message("Successfully created the process!");

		/* Read the pipe, if it isn't headless */
		if(!headless)
		{
			/* Close the write handle -- if we don't do this, the ReadFile() coming up gets stuck. */
			CloseHandle(stdout_write);

			/* Read from the pipe. */
			log_message("Attempting to read from the pipe");
			while(ReadFile(stdout_read, buffer, 1023, &bytes_read, NULL))
			{
				if(strlen(readfile) == 0)
					output(num, buffer, bytes_read);
			}
			CloseHandle(stdout_read);

			/* If we're reading an output file instead of stdout, do it here. */
			if(strlen(readfile) > 0)
			{
				FILE *read;
				errno_t err;

				log_message("Trying to open output file: %s", readfile);
				err = fopen_s(&read, readfile, "rb");

				if(err)
				{
					log_message("Couldn't open the readfile: %d", err);
					output(num, "Couldn't open the output file", -1);
				}
				else
				{
					char buf[1024];
					int count;

					count = fread(buf, 1, 1024, read);
					while(count)
					{
						output(num, buf, count);
						count = fread(buf, 1, 1024, read);
					}

					fclose(read);
				}
			}
		}
		else
		{
			output(num, "Process has been created", -1);
		}

		log_message("Done!");
	}
}

// Control handler function
static void ControlHandler(DWORD request)
{
	switch(request)
	{
		case SERVICE_CONTROL_STOP:

			ServiceStatus.dwWin32ExitCode = 0;
			ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
			SetServiceStatus (hStatus, &ServiceStatus);
			return;

		case SERVICE_CONTROL_SHUTDOWN:

			ServiceStatus.dwWin32ExitCode = 0;
			ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
			SetServiceStatus (hStatus, &ServiceStatus);
			return;

		default:
			break;
	}

	SetServiceStatus(hStatus,  &ServiceStatus);
}



static void die(int err)
{
	// Not enough arguments
	ServiceStatus.dwCurrentState  = SERVICE_STOPPED;
	ServiceStatus.dwWin32ExitCode = err;
	SetServiceStatus(hStatus, &ServiceStatus);
}

static void ServiceMain(int argc, char** argv)
{
	char   *outfile_name;
	char   *tempfile_name;
	int     count;
	int     logging;
	int     result;
	int     i;
	char   *current_directory;

	/* Make sure we got the minimum number of arguments. */
	if(argc < 6)
		return;

	/* Read the arguments. */
	outfile_name      = argv[1];
	tempfile_name     = argv[2];
	count             = atoi(argv[3]);
	logging           = atoi(argv[4]);
	enc_key           = argv[5];
	enc_key_loc       = 0;
	current_directory = argv[6];

	/* If they didn't turn on logging, disable it. */
	if(logging != 1)
		log_message(NULL);

	/* Log the state. */
	log_message("");
	log_message("-----------------------");
	log_message("STARTING");

	/* Log all the arguments. */
	log_message("Arguments: %d\n", argc);
	for(i = 0; i < argc; i++)
		log_message("Argument %d: %s", i, argv[i]);

	/* Set up the service. Likely unnecessary for what we're doing, but it doesn't hurt. */
	ServiceStatus.dwServiceType             = SERVICE_WIN32;
	ServiceStatus.dwCurrentState            = SERVICE_RUNNING;
	ServiceStatus.dwControlsAccepted        = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
	ServiceStatus.dwWin32ExitCode           = 0;
	ServiceStatus.dwServiceSpecificExitCode = 0;
	ServiceStatus.dwCheckPoint              = 0;
	ServiceStatus.dwWaitHint                = 0;
	hStatus = RegisterServiceCtrlHandler("", (LPHANDLER_FUNCTION)ControlHandler);
	SetServiceStatus(hStatus, &ServiceStatus);

	/* Registering Control Handler failed (this is a bit late, but eh?) */
	if(hStatus == (SERVICE_STATUS_HANDLE)0)
	{
		log_message("Service failed to start");
		die(-1);
		return;
	}

	/* Set the current directory. */
	SetCurrentDirectory(current_directory);

	/* Open the output file. */
	log_message("Opening temporary output file: %s", tempfile_name);

	/* Open the outfile. */
	if(result = fopen_s(&outfile, tempfile_name, "wb"))
	{
		log_message("Couldn't open output file: %d", result);
		die(-1);
	}
	else
	{
		/* Run the programs we were given. */
		for(i = 0; i < count; i++)
		{
			char *program        = argv[(i*5) + 7];
			char *env            = argv[(i*5) + 8];
			char *headless       = argv[(i*5) + 9];
			char *include_stderr = argv[(i*5) + 10];
			char *read_file      = argv[(i*5) + 11];

			go(i, program, env, !strcmp(headless, "true"), !strcmp(include_stderr, "true"), read_file);
		}

		/* Close the output file. */
		if(fclose(outfile))
			log_message("Couldn't close the file: %d", errno);

		/* Rename the output file (this is what tells Nmap we're done. */
		log_message("Renaming file %s => %s", tempfile_name, outfile_name);

		/* I noticed that sometimes, programs inherit the handle to the file (or something), so I can't change it right
		 * away. For this reason, allow about 10 seconds to move it. */
		for(i = 0; i < 10; i++)
		{
			if(rename(tempfile_name, outfile_name))
			{
				log_message("Couldn't rename file: %d (will try %d more times)", errno, 10 - i - 1);
			}
			else
			{
				log_message("File successfully renamed!");
				break;
			}

			Sleep(1000);
		}

		/* Clean up and stop the service. */
		die(0);
	}
}

int main(int argc, char *argv[])
{
	SERVICE_TABLE_ENTRY ServiceTable[2];
	ServiceTable[0].lpServiceName = "";
	ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

	ServiceTable[1].lpServiceName = NULL;
	ServiceTable[1].lpServiceProc = NULL;
	// Start the control dispatcher thread for our service
	StartServiceCtrlDispatcher(ServiceTable);
}