From 85f3680622da0eb3529e042793d4fbf9b0d41110 Mon Sep 17 00:00:00 2001 From: Martin Szulecki Date: Sat, 21 Jun 2014 01:53:31 +0200 Subject: Add protocol implementation for debugserver service --- src/debugserver.c | 620 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 src/debugserver.c (limited to 'src/debugserver.c') diff --git a/src/debugserver.c b/src/debugserver.c new file mode 100644 index 0000000..1e38698 --- /dev/null +++ b/src/debugserver.c @@ -0,0 +1,620 @@ +/* + * debugserver.c + * com.apple.debugserver service implementation. + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include + +#include "debugserver.h" +#include "lockdown.h" +#include "common/debug.h" +#include "common/utils.h" +#include "asprintf.h" + +/** + * Convert a service_error_t value to a debugserver_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching debugserver_error_t error code, + * DEBUGSERVER_E_UNKNOWN_ERROR otherwise. + */ +static debugserver_error_t debugserver_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return DEBUGSERVER_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return DEBUGSERVER_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return DEBUGSERVER_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return DEBUGSERVER_E_SSL_ERROR; + default: + break; + } + return DEBUGSERVER_E_UNKNOWN_ERROR; +} + +debugserver_error_t debugserver_client_new(idevice_t device, lockdownd_service_descriptor_t service, debugserver_client_t* client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to debugserver_client_new."); + return DEBUGSERVER_E_INVALID_ARG; + } + + debug_info("Creating debugserver_client, port = %d.", service->port); + + service_client_t parent = NULL; + debugserver_error_t ret = debugserver_error(service_client_new(device, service, &parent)); + if (ret != DEBUGSERVER_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + debugserver_client_t client_loc = (debugserver_client_t) malloc(sizeof(struct debugserver_client_private)); + client_loc->parent = parent; + client_loc->noack_mode = 0; + + *client = client_loc; + + debug_info("debugserver_client successfully created."); + return 0; +} + +debugserver_error_t debugserver_client_start_service(idevice_t device, debugserver_client_t * client, const char* label) +{ + debugserver_error_t err = DEBUGSERVER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DEBUGSERVER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(debugserver_client_new), &err); + return err; +} + +debugserver_error_t debugserver_client_free(debugserver_client_t client) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t err = debugserver_error(service_client_free(client->parent)); + client->parent = NULL; + free(client); + + return err; +} + +debugserver_error_t debugserver_client_send(debugserver_client_t client, const char* data, uint32_t size, uint32_t *sent) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return SERVICE_E_INVALID_ARG; + } + + debug_info("sending %d bytes", size); + res = debugserver_error(service_send(client->parent, data, size, (uint32_t*)&bytes)); + if (bytes <= 0) { + debug_info("ERROR: sending to device failed."); + } + if (sent) { + *sent = (uint32_t)bytes; + } + + return res; +} + +debugserver_error_t debugserver_client_receive_with_timeout(debugserver_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return DEBUGSERVER_E_INVALID_ARG; + } + + res = debugserver_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (bytes <= 0) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return res; +} + +debugserver_error_t debugserver_client_receive(debugserver_client_t client, char* data, uint32_t size, uint32_t *received) +{ + return debugserver_client_receive_with_timeout(client, data, size, received, 1000); +} + +debugserver_error_t debugserver_command_new(const char* name, int argc, const char* argv[], debugserver_command_t* command) +{ + int i; + debugserver_command_t tmp = (debugserver_command_t) malloc(sizeof(struct debugserver_command_private)); + + /* copy name */ + tmp->name = strdup(name); + + /* copy arguments */ + tmp->argc = argc; + tmp->argv = NULL; + if (argc > 0) { + tmp->argv = malloc(sizeof(char*) * (argc + 2)); + for (i = 0; i < argc; i++) { + tmp->argv[i] = strdup(argv[i]); + } + tmp->argv[i+1] = NULL; + } + + /* return */ + *command = tmp; + + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_command_free(debugserver_command_t command) +{ + int i; + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + + if (!command) + return DEBUGSERVER_E_INVALID_ARG; + + if (command) { + if (command->name) + free(command->name); + if (command->argv && command->argc) { + for (i = 0; i < command->argc; i++) { + free(command->argv[i]); + } + free(command->argv); + } + free(command); + res = DEBUGSERVER_E_SUCCESS; + } + + return res; +} + +static int debugserver_hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + else if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + else + return c; +} + +static char debugserver_int2hex(int x) +{ + const char *hexchars = "0123456789ABCDEF"; + return hexchars[x]; +} + +#define DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(byte) debugserver_int2hex((byte >> 0x4) & 0xf) +#define DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(byte) debugserver_int2hex(byte & 0xf) +#define DEBUGSERVER_HEX_DECODE_FIRST_BYTE(byte) ((byte >> 0x4) & 0xf) +#define DEBUGSERVER_HEX_DECODE_SECOND_BYTE(byte) (byte & 0xf) + +static uint32_t debugserver_get_checksum_for_buffer(const char* buffer, uint32_t size) +{ + uint32_t checksum = 0; + uint32_t i; + + for (i = 0; i < size; i++) { + checksum += buffer[i]; + } + + return checksum; +} + +static int debugserver_response_is_checksum_valid(const char* response, uint32_t size) +{ + uint32_t checksum = 0; + if ((size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1) > 0) + checksum = debugserver_get_checksum_for_buffer(&response[1], size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1); + + debug_info("checksum: 0x%x", checksum); + + if ((unsigned)debugserver_hex2int(response[size - 2]) != DEBUGSERVER_HEX_DECODE_FIRST_BYTE(checksum)) + return 0; + + if ((unsigned)debugserver_hex2int(response[size - 1]) != DEBUGSERVER_HEX_DECODE_SECOND_BYTE(checksum)) + return 0; + + debug_info("valid checksum"); + + return 1; +} + +void debugserver_encode_string(const char* buffer, char** encoded_buffer, uint32_t* encoded_length) +{ + uint32_t position; + uint32_t index; + uint32_t length = strlen(buffer); + *encoded_length = (2 * length) + DEBUGSERVER_CHECKSUM_HASH_LENGTH + 1; + + *encoded_buffer = malloc(sizeof(char) * (*encoded_length)); + memset(*encoded_buffer, '\0', *encoded_length); + for (position = 0, index = 0; index < length; index++) { + position = (index * (2 * sizeof(char))); + (*encoded_buffer)[position] = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(buffer[index]); + (*encoded_buffer)[position + 1] = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(buffer[index]); + } +} + +void debugserver_decode_string(const char *encoded_buffer, size_t encoded_length, char** buffer) +{ + *buffer = malloc(sizeof(char) * ((encoded_length / 2)+1)); + char* t = *buffer; + const char *f = encoded_buffer; + const char *fend = f + encoded_length; + while (f < fend) { + *t++ = debugserver_hex2int(*f) << 4 | debugserver_hex2int(f[1]); + f += 2; + } + *t = '\0'; +} + +static void debugserver_format_command(const char* prefix, const char* command, const char* arguments, int calculate_checksum, char** buffer, uint32_t* size) +{ + char checksum_hash[DEBUGSERVER_CHECKSUM_HASH_LENGTH + 1] = {'#', '0', '0', '\0'}; + char* encoded = NULL; + uint32_t encoded_length = 0; + + if (arguments) { + /* arguments must be hex encoded */ + debugserver_encode_string(arguments, &encoded, &encoded_length); + } else { + encoded = NULL; + } + + char* encoded_command = string_concat(command, encoded, NULL); + encoded_length = strlen(encoded_command); + + if (calculate_checksum) { + uint32_t checksum = debugserver_get_checksum_for_buffer(encoded_command, encoded_length); + checksum_hash[1] = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(checksum); + checksum_hash[2] = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(checksum); + } + + *buffer = string_concat(prefix, encoded_command, checksum_hash, NULL); + *size = strlen(prefix) + strlen(encoded_command) + DEBUGSERVER_CHECKSUM_HASH_LENGTH; + + debug_info("formatted command: %s size: %d checksum: 0x%s", *buffer, *size, checksum_hash); + + if (encoded_command) + free(encoded_command); + + if (encoded) + free(encoded); +} + +static debugserver_error_t debugserver_client_send_ack(debugserver_client_t client) +{ + debug_info("sending ACK"); + return debugserver_client_send(client, "+", sizeof(char), NULL); +} + +static debugserver_error_t debugserver_client_send_noack(debugserver_client_t client) +{ + debug_info("sending !ACK"); + return debugserver_client_send(client, "-", sizeof(char), NULL); +} + +static debugserver_error_t debugserver_client_set_ack_mode(debugserver_client_t client, int enabled) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + client->noack_mode = (enabled == 0)? 1: 0; + + debug_info("ack mode: %s", client->noack_mode == 0 ? "on": "off"); + + return DEBUGSERVER_E_SUCCESS; +} + +static int debugserver_client_receive_internal_check(debugserver_client_t client, char* received_char) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + int did_receive_char = 0; + char buffer = 0; + uint32_t bytes = 0; + + /* we loop here as we expect an answer */ + res = debugserver_client_receive_with_timeout(client, &buffer, sizeof(char), &bytes, 1000); + if (res == DEBUGSERVER_E_SUCCESS && received_char[0] != 0) { + if (memcmp(&buffer, received_char, sizeof(char)) == 0) { + did_receive_char = 1; + } + } else { + did_receive_char = 0; + } + + if (!did_receive_char) { + memcpy(received_char, &buffer, sizeof(char)); + } + + return did_receive_char; +} + +debugserver_error_t debugserver_client_receive_response(debugserver_client_t client, char** response) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + + int should_receive = 1; + int skip_prefix = 0; + char* command_prefix = strdup("$"); + + char* buffer = NULL; + uint32_t buffer_size = 0; + + if (response) + *response = NULL; + + if (!client->noack_mode) { + char ack[2] = {'+', '\0'}; + debug_info("attempting to receive ACK %c", *ack); + should_receive = debugserver_client_receive_internal_check(client, ack); + debug_info("received char: %c", *ack); + if (strncmp(ack, command_prefix, sizeof(char)) == 0) { + should_receive = 1; + skip_prefix = 1; + buffer = strdup(command_prefix); + buffer_size += sizeof(char); + debug_info("received ACK"); + } + } + + debug_info("should_receive: %d, skip_prefix: %d", should_receive, skip_prefix); + + if (should_receive && !skip_prefix) { + debug_info("attempting to receive prefix"); + should_receive = debugserver_client_receive_internal_check(client, command_prefix); + debug_info("received command_prefix: %c", *command_prefix); + if (should_receive) { + if (buffer) { + memcpy(buffer, command_prefix, sizeof(char)); + } else { + buffer = strdup(command_prefix); + buffer_size += sizeof(char); + } + } + } + + debug_info("buffer: %*s, should_receive: %d, skip_prefix: %d", buffer_size, buffer, should_receive, skip_prefix); + + if (should_receive) { + uint32_t checksum_length = DEBUGSERVER_CHECKSUM_HASH_LENGTH; + int receiving_checksum_response = 0; + debug_info("attempting to read up response until checksum"); + while ((checksum_length > 0)) { + char data[2] = {'#', '\0'}; + if (debugserver_client_receive_internal_check(client, data)) { + receiving_checksum_response = 1; + } + if (receiving_checksum_response) { + checksum_length--; + } + char* newbuffer = string_concat(buffer, data, NULL); + buffer_size += sizeof(char); + free(buffer); + buffer = NULL; + buffer = newbuffer; + newbuffer = NULL; + } + debug_info("validating response checksum..."); + int valid_response = debugserver_response_is_checksum_valid(buffer, buffer_size); + if (valid_response) { + if (response) { + /* assemble response string */ + uint32_t response_size = sizeof(char) * (buffer_size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1); + *response = (char*)malloc(response_size + 1); + memcpy(*response, buffer + 1, response_size); + (*response)[response_size] = '\0'; + } + if (!client->noack_mode) { + /* confirm valid command */ + debugserver_client_send_ack(client); + } + } else { + /* response was invalid */ + res = DEBUGSERVER_E_RESPONSE_ERROR; + if (!client->noack_mode) { + /* report invalid command */ + debugserver_client_send_noack(client); + } + } + } + + if (response) + debug_info("response: %s", *response); + + if (buffer) + free(buffer); + + if (command_prefix) + free(command_prefix); + + return res; +} + +debugserver_error_t debugserver_client_send_command(debugserver_client_t client, debugserver_command_t command, char** response) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + int i; + uint32_t bytes = 0; + + char* send_buffer = NULL; + uint32_t send_buffer_size = 0; + + char* command_arguments = NULL; + + /* concat all arguments */ + char* tmp = NULL; + char* newtmp = NULL; + for (i = 0; i < command->argc; i++) { + debug_info("argv[%d]: %s", i, command->argv[i]); + if (!tmp) { + tmp = strdup(command->argv[i]); + } else { + newtmp = string_concat(tmp, command->argv[i], NULL); + free(tmp); + tmp = newtmp; + } + } + command_arguments = tmp; + tmp = NULL; + + debug_info("command_arguments(%d): %s", command->argc, command_arguments); + + /* encode command arguments, add checksum if required and assemble entire command */ + debugserver_format_command("$", command->name, command_arguments, !client->noack_mode, &send_buffer, &send_buffer_size); + + debug_info("sending encoded command: %s", send_buffer); + + res = debugserver_client_send(client, send_buffer, send_buffer_size, &bytes); + debug_info("command result: %d", res); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + + /* receive response */ + res = debugserver_client_receive_response(client, response); + debug_info("response result: %d", res); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + + if (response) + debug_info("received response: %s", *response); + + /* disable sending ack on the client */ + if (!strncmp(command->name, "QStartNoAckMode", 16)) { + debugserver_client_set_ack_mode(client, 0); + } + +cleanup: + if (command_arguments) + free(command_arguments); + + if (send_buffer) + free(send_buffer); + + return res; +} + +debugserver_error_t debugserver_client_set_environment_hex_encoded(debugserver_client_t client, const char* env, char** response) +{ + if (!client || !env) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t result = DEBUGSERVER_E_UNKNOWN_ERROR; + const char* env_arg[] = { env, NULL }; + + debugserver_command_t command = NULL; + debugserver_command_new("QEnvironmentHexEncoded:", 1, env_arg, &command); + result = debugserver_client_send_command(client, command, response); + debugserver_command_free(command); + + return result; +} + +debugserver_error_t debugserver_client_set_argv(debugserver_client_t client, int argc, char* argv[], char** response) +{ + if (!client || !argc) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t result = DEBUGSERVER_E_UNKNOWN_ERROR; + char *pkt = NULL; + int pkt_len = 0; + int i = 0; + + /* calculate total length */ + while (i < argc && argv && argv[i]) { + char *prefix = NULL; + asprintf(&prefix, ",%d,%d,", (int)strlen(argv[i]) * 2, i); + pkt_len += (int)strlen(prefix) + (int)strlen(argv[i]) * 2; + free(prefix); + i++; + } + + /* allocate packet and initialize it */ + pkt = (char *) malloc(pkt_len + 1); + memset(pkt, 0, pkt_len + 1); + + char *pktp = pkt; + + i = 0; + while (i < argc && argv && argv[i]) { + debug_info("argv[%d] = \"%s\"", i, argv[i]); + + char *prefix = NULL; + char *m = NULL; + int arg_len = strlen(argv[i]); + int arg_hexlen = arg_len * 2; + + asprintf(&prefix, ",%d,%d,", arg_hexlen, i); + + m = (char *) malloc(arg_hexlen); + char *p = m; + char *q = (char*)argv[i]; + while (*q) { + *p++ = debugserver_int2hex(*q >> 4); + *p++ = debugserver_int2hex(*q & 0xf); + q++; + } + + memcpy(pktp, prefix, strlen(prefix)); + pktp += strlen(prefix); + + memcpy(pktp, m, arg_hexlen); + pktp += arg_hexlen; + + free(prefix); + free(m); + + i++; + } + + pkt[0] = 'A'; + + debugserver_command_t command = NULL; + debugserver_command_new(pkt, 0, NULL, &command); + result = debugserver_client_send_command(client, command, response); + debugserver_command_free(command); + + if (pkt) + free(pkt); + + return result; +} -- cgit v1.1-32-gdbae