1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-03-18 15:14:10 +01:00

551 lines
12 KiB
C

#include "metcli.h"
// Core console commands
extern DWORD cmd_open(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_read(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_write(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_close(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_interact(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_help(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_exit(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_loadlib(Remote *remote, UINT argc, CHAR **argv);
extern DWORD cmd_use(Remote *remote, UINT argc, CHAR **argv);
/*
* Local client core command line dispatch table
*/
ConsoleCommand consoleCommands[] =
{
// Core extensions
{ "Core", NULL, "Core feature set commands", 1 },
{ "open", cmd_open, "Opens a communication channel.", 0 },
{ "read", cmd_read, "Reads from a communication channel.", 0 },
{ "write", cmd_write, "Writes to a communication channel.", 0 },
{ "close", cmd_close, "Closes a communication channel.", 0 },
{ "interact", cmd_interact, "Switch to interactive mode with a channel.", 0 },
{ "help", cmd_help, "Displays a list of commands.", 0 },
{ "exit", cmd_exit, "Exits the client.", 0 },
// Feature extensions
{ "Features", NULL, "Feature extension commands", 1 },
{ "loadlib", cmd_loadlib, "Load a library on the remote machine.", 0 },
{ "use", cmd_use, "Use a feature module.", 0 },
// Terminator
{ NULL, NULL, NULL, 0 },
};
VOID console_read_thread_func(Remote *remote);
ConsoleCommand *extendedCommandsHead = NULL;
ConsoleCommand *extendedCommandsTail = NULL;
Channel *interactiveChannel = NULL;
DWORD interactiveChannelId = 0;
PCHAR inputBuffer = NULL;
ULONG inputBufferLength = 0;
HANDLE consoleReadThread = NULL;
#ifdef _WIN32
/*
* Enable command history on the console and create the interactive console
* for future use.
*/
VOID console_initialize(Remote *remote)
{
BOOL (WINAPI *setConsoleInputExeName)(LPCSTR base) = NULL;
CHAR name[1024];
PCHAR slash;
float init = 1.1f; // VC++ requires float usage to use float libraries.
DWORD mode = 0;
DWORD tid;
do
{
// Locate the SetConsoleInputExeNameA routine for use with custom
// history tracking
if (!((LPVOID)setConsoleInputExeName =
(LPVOID)GetProcAddress(GetModuleHandle("kernel32"),
"SetConsoleInputExeNameA")))
break;
memset(name, 0, sizeof(name));
if (!GetModuleFileName(
GetModuleHandle(0),
name,
sizeof(name) - 1))
break;
if (!(slash = strrchr(name, '\\')))
break;
// investigate
setConsoleInputExeName(name);
// Set the console window's title
SetConsoleTitle("meterpreter");
consoleReadThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)console_read_thread_func, remote,
0, &tid);
} while (0);
}
/*
* Write a format string buffer to the console
*/
VOID console_write_output(LPCSTR fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stdout, fmt, ap);
va_end(ap);
}
/*
* Write raw output to the console
*/
VOID console_write_output_raw(PUCHAR buf, ULONG length)
{
HANDLE pStdout = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written = 0;
WriteFile(pStdout, buf, length, &written, NULL);
}
/*
* Write the console prompt to the screen
*/
VOID console_write_prompt()
{
fprintf(stdout, "meterpreter> ");
fflush(stdout);
}
/*
* Generic output of success/fail
*/
DWORD console_generic_response_output(Remote *remote, Packet *packet,
LPCSTR subsys, LPCSTR cmd)
{
DWORD res = packet_get_result(packet);
if (res == ERROR_SUCCESS)
console_write_output(
"\n"
INBOUND_PREFIX " %s: %s succeeded.\n", subsys, cmd);
else
console_write_output(
"\n"
INBOUND_PREFIX " %s: %s failed, result %lu.\n",
subsys, cmd, packet_get_result(packet));
console_write_prompt();
return res;
}
/*
* Check to see if an escape sequence has been sent to the console
*
* The escape sequence is: ESC
*/
BOOL console_check_escape_sent()
{
BOOL escapeSent = FALSE;
INPUT_RECORD r[32];
DWORD numRead = 0;
if (PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE),
r, 32, &numRead))
{
DWORD index = 0;
for (index = 0;
(!escapeSent) && (index < numRead);
index++)
{
if (r[index].EventType != KEY_EVENT)
continue;
// If the control key is pressed and the VK is escape..
if (r[index].Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE)
escapeSent = TRUE;
}
}
return escapeSent;
}
#endif
/*
* Set the interactive channel for input/output overriding
*/
VOID console_set_interactive_channel(Remote *remote, Channel *channel)
{
// If an interactive channel is use, unset the interactive flag
if (interactiveChannel)
channel_interact(interactiveChannel, remote, NULL, 0, FALSE,
NULL);
interactiveChannel = channel;
interactiveChannelId = (channel) ? channel_get_id(channel) : 0;
}
/*
* Get the interactive channel descriptor
*/
Channel *console_get_interactive_channel()
{
return interactiveChannel;
}
/*
* Get the interactive channel indentifier, if any
*/
DWORD console_get_interactive_channel_id()
{
return interactiveChannelId;
}
/*
* Process a remote cmomand when data is available
*/
DWORD console_remote_notify(Remote *remote, HANDLE notify)
{
DWORD res;
ResetEvent(notify);
res = command_process_remote(remote, NULL);
return res;
}
/*
* Process console commands in a loop
*
* I would use the scheduler but allowing the file descriptor to drop
* into non-blocking mode makes things annoying.
*/
VOID console_process_commands(Remote *remote)
{
SOCKET fd = remote_get_fd(remote);
struct timeval tv;
fd_set fdread;
LONG r;
console_write_prompt();
// Execute the scheduler in a loop
while (1)
{
FD_ZERO(&fdread);
FD_SET(fd, &fdread);
tv.tv_sec = 0;
tv.tv_usec = 100;
if ((r = select(fd + 1, &fdread, NULL, NULL, &tv)) > 0)
{
LONG bytes = 0;
ioctlsocket(fd, FIONREAD, &bytes);
if (bytes == 0)
{
console_write_output(
"\n"
"Connection reset by peer.\n");
break;
}
command_process_remote(remote, NULL);
}
else if (r < 0)
break;
scheduler_run(remote, 0);
}
}
VOID console_read_thread_func(Remote *remote)
{
while (1)
console_read_buffer(remote);
}
/*
* Reads in data from the input device, potentially calling the
* command processing function if a complete command has been read.
*/
VOID console_read_buffer(Remote *remote)
{
DWORD newInputBufferLength, stringLength, offset;
Channel *interactiveChannel;
PCHAR newInputBuffer;
BOOL process = FALSE;
CHAR buf[4096];
PCHAR eoln, eolr;
LONG bytesRead;
// Ensure null termination
buf[sizeof(buf) - 1] = 0;
do
{
// Is there data available?
if (WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE), INFINITE)
!= WAIT_OBJECT_0)
break;
// If a console escape character was sent and we're currently interactive,
// break out of interactive mode
if ((console_check_escape_sent()) &&
(console_get_interactive_channel()))
{
console_set_interactive_channel(remote, NULL);
console_write_output(
"\n"
"\n"
"Exiting interactive mode..\n");
console_write_prompt();
}
// Read the command
if ((!ReadConsole(GetStdHandle(STD_INPUT_HANDLE),
buf, sizeof(buf) - 1, &bytesRead, NULL)) || (bytesRead <= 0))
break;
buf[bytesRead] = 0;
// If an interactive channel is in use, write directly to it.
if ((interactiveChannel = console_get_interactive_channel()))
{
channel_write(interactiveChannel, remote, NULL, 0, buf,
bytesRead, NULL);
break;
}
if ((eoln = strchr(buf, '\n')))
{
*eoln = 0;
process = TRUE;
}
// Remove end of line characters
if ((eolr = strchr(buf, '\r')))
*eolr = 0;
// Calculate lengths
stringLength = strlen(buf);
newInputBufferLength = inputBufferLength + stringLength;
if (inputBuffer)
newInputBuffer = (PCHAR)realloc(inputBuffer,
newInputBufferLength);
else
newInputBuffer = (PCHAR)malloc(++newInputBufferLength);
// Allocation failure?
if (!newInputBuffer)
break;
if ((offset = inputBufferLength))
offset--;
// Copy the string
memcpy(newInputBuffer + offset, buf, stringLength);
// Update the input buffer
inputBuffer = newInputBuffer;
inputBufferLength = newInputBufferLength;
// Process the full command line if it's completed
if (process)
{
inputBuffer[inputBufferLength - 1] = 0;
client_acquire_lock();
console_process_command(remote);
client_release_lock();
free(inputBuffer);
inputBuffer = NULL;
inputBufferLength = 0;
console_write_prompt();
}
} while (0);
}
/*
* Parse the local command into an argument vector
*
* TODO:
*
* - Add character unescaping (\x01)
*/
VOID console_process_command(Remote *remote)
{
CHAR **argv = NULL, *current;
ConsoleCommand *command = NULL;
UINT argc, index;
do
{
// Calculate the number of arguments
for (current = inputBuffer, argc = 1;
current = strchr(current, ' ');
current++, argc++);
current = inputBuffer;
index = 0;
if (!(argv = (CHAR **)malloc(sizeof(PCHAR) * argc)))
break;
// Populate the argument vector
while (1)
{
CHAR *space = NULL, *edquote = NULL;
// If the first character of the current argument is a quote,
// find the next quote.
if (current[0] == '"')
{
if ((edquote = strchr(current + 1, '"')))
*edquote = 0;
}
else if ((space = strchr(current, ' ')))
*space = 0;
// If we're using quoting for this argument, skip one past current.
argv[index++] = _strdup(current + ((edquote) ? 1 : 0));
current = ((edquote) ? edquote : space) + 1;
if (space)
*space = ' ';
else if (edquote)
*edquote = '"';
else
break;
}
// Find the command
for (index = 0;
consoleCommands[index].name;
index++)
{
if (!strcmp(consoleCommands[index].name, argv[0]))
{
command = &consoleCommands[index];
break;
}
}
// If the command was not found in the default command list, try looking
// in the extended list
if (!command)
{
for (command = extendedCommandsHead;
command;
command = command->next)
{
if (!strcmp(command->name, argv[0]))
break;
}
}
// The command was not found.
if ((!command) || (!command->name))
break;
command->handler(remote, argc, argv);
} while (0);
// Cleanup argv
if (argv)
{
for (index = 0;
index < argc;
index++)
free(argv[index]);
free(argv);
}
}
/*
* Dynamically registers a client command
*/
DWORD console_register_command(ConsoleCommand *command)
{
ConsoleCommand *newConsoleCommand;
if (!(newConsoleCommand = (ConsoleCommand *)malloc(sizeof(ConsoleCommand))))
return ERROR_NOT_ENOUGH_MEMORY;
memcpy(newConsoleCommand, command, sizeof(ConsoleCommand));
if (extendedCommandsTail)
extendedCommandsTail->next = newConsoleCommand;
newConsoleCommand->prev = extendedCommandsTail;
newConsoleCommand->next = NULL;
extendedCommandsTail = newConsoleCommand;
if (!extendedCommandsHead)
extendedCommandsHead = newConsoleCommand;
return ERROR_SUCCESS;
}
/*
* Dynamically deregisters a client command
*/
DWORD console_deregister_command(ConsoleCommand *command)
{
ConsoleCommand *current, *prev;
DWORD res = ERROR_NOT_FOUND;
// Search the extension list for the command
for (current = extendedCommandsHead, prev = NULL;
current;
prev = current, current = current->next)
{
if (strcmp(command->name, current->name))
continue;
if (prev)
prev->next = current->next;
else
extendedCommandsHead = current->next;
if (current->next)
current->next->prev = prev;
if (current == extendedCommandsTail)
extendedCommandsTail = current->prev;
// Deallocate it
free(current);
res = ERROR_SUCCESS;
break;
}
return res;
}