From a6775bc588db13838bebec42b139748d337e7189 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Sat, 10 Dec 2022 02:22:12 +0100 Subject: tools: Add idevicedevmodectl tool --- docs/Makefile.am | 1 + docs/idevicedevmodectl.1 | 58 ++++++ tools/Makefile.am | 6 + tools/idevicedevmodectl.c | 451 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 docs/idevicedevmodectl.1 create mode 100644 tools/idevicedevmodectl.c diff --git a/docs/Makefile.am b/docs/Makefile.am index 9cdf82e..4a4c56f 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -16,6 +16,7 @@ man_MANS = \ idevicecrashreport.1 \ idevicename.1 \ idevicedebug.1 \ + idevicedevmodectl.1 \ idevicenotificationproxy.1 \ idevicesetlocation.1 diff --git a/docs/idevicedevmodectl.1 b/docs/idevicedevmodectl.1 new file mode 100644 index 0000000..5edaa80 --- /dev/null +++ b/docs/idevicedevmodectl.1 @@ -0,0 +1,58 @@ +.TH "idevicedevmodectl" 1 +.SH NAME +idevicedevmodectl \- Enable Developer Mode on iOS 16+ devices or print the current status. +.SH SYNOPSIS +.B idevicedevmodectl +COMMAND +[OPTIONS] + +.SH DESCRIPTION + +Enable Developer Mode on iOS 16+ devices or print the current status. + +.SH NOTE +Passcode-protected devices will NOT allow enabling of Developer Mode from the command line. It has to be enabled on the device itself under Settings -> Privacy & Security -> Developer Mode. +The \f[B]enable\f[] command will try to enable it, and tell you if that's the case. +If the menu is not shown, you may use the \f[B]reveal\f[] command to reveal it. + +.SH COMMANDS +.TP +.B list +Prints the Developer Mode status of all connected devices, or for a specific one if \f[B]\-\-udid\f[] is given. +.TP +.B enable +Enable Developer Mode (device will reboot), and confirm it after device booted up again. +.TP +.B arm +Arm the Developer Mode (device will reboot) +.TP +.B confirm +Confirm enabling of Developer Mode +.TP +.B reveal +Reveal the Developer Mode menu on the device under Settings -> Privacy & Security + +.SH OPTIONS +.TP +.B \-u, \-\-udid UDID +target specific device by UDID +.TP +.B \-n, \-\-network +connect to network device +.TP +.B \-d, \-\-debug +enable communication debugging +.TP +.B \-h, \-\-help +print usage information +.TP +.B \-v, \-\-version +print version information + +.SH AUTHORS +Nikias Bassen + +.SH ON THE WEB +https://libimobiledevice.org + +https://github.com/libimobiledevice/libimobiledevice diff --git a/tools/Makefile.am b/tools/Makefile.am index e8ef3ab..47e05b2 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -27,6 +27,7 @@ bin_PROGRAMS = \ idevicedebugserverproxy \ idevicediagnostics \ idevicedebug \ + idevicedevmodectl \ idevicenotificationproxy \ idevicecrashreport \ idevicesetlocation @@ -111,6 +112,11 @@ idevicedebug_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) idevicedebug_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) idevicedebug_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la +idevicedevmodectl_SOURCES = idevicedevmodectl.c +idevicedevmodectl_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) +idevicedevmodectl_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) +idevicedevmodectl_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la + idevicenotificationproxy_SOURCES = idevicenotificationproxy.c idevicenotificationproxy_CFLAGS = $(AM_CFLAGS) idevicenotificationproxy_LDFLAGS = $(AM_LDFLAGS) diff --git a/tools/idevicedevmodectl.c b/tools/idevicedevmodectl.c new file mode 100644 index 0000000..739bc13 --- /dev/null +++ b/tools/idevicedevmodectl.c @@ -0,0 +1,451 @@ +/* + * idevicedevmodectl.c + * List or enable Developer Mode on iOS 16+ devices + * + * Copyright (c) 2022 Nikias Bassen, 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 + +#define TOOL_NAME "idevicedevmodectl" + +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#endif + +#ifdef WIN32 +#include +#define __usleep(x) Sleep(x/1000) +#else +#include +#define __usleep(x) usleep(x) +#endif + +#include +#include +#include +#include + +#define AMFI_LOCKDOWN_SERVICE_NAME "com.apple.amfi.lockdown" + +static char* udid = NULL; +static int use_network = 0; + +static void print_usage(int argc, char **argv, int is_error) +{ + char *name = strrchr(argv[0], '/'); + fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0])); + fprintf(is_error ? stderr : stdout, + "\n" + "Enable Developer Mode on iOS 16+ devices or print the current status.\n" + "\n" + "Where COMMAND is one of:\n" + " list Print the Developer Mode status of all connected devices\n" + " or for a specific one if --udid is given.\n" + " enable Enable Developer Mode (device will reboot),\n" + " and confirm it after device booted up again.\n" + "\n" + " arm Arm the Developer Mode (device will reboot)\n" + " confirm Confirm enabling of Developer Mode\n" + " reveal Reveal the Developer Mode menu on the device\n" + "\n" + "The following OPTIONS are accepted:\n" + " -u, --udid UDID target specific device by UDID\n" + " -n, --network connect to network device\n" + " -d, --debug enable communication debugging\n" + " -h, --help print usage information\n" + " -v, --version print version information\n" + "\n" + "Homepage: <" PACKAGE_URL ">\n" + "Bug Reports: <" PACKAGE_BUGREPORT ">\n" + ); +} + +enum { + OP_LIST, + OP_ENABLE, + OP_ARM, + OP_CONFIRM, + OP_REVEAL, + NUM_OPS +}; +#define DEV_MODE_REVEAL 0 +#define DEV_MODE_ARM 1 +#define DEV_MODE_ENABLE 2 + +static int get_developer_mode_status(const char* device_udid, int _use_network) +{ + idevice_error_t ret; + idevice_t device = NULL; + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR; + plist_t val = NULL; + + ret = idevice_new_with_options(&device, device_udid, (_use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + return -1; + } + + if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + idevice_free(device); + return -1; + } + + lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr)); + lockdownd_client_free(lockdown); + idevice_free(device); + return -2; + } + + uint8_t dev_mode_status = 0; + plist_get_bool_val(val, &dev_mode_status); + plist_free(val); + + lockdownd_client_free(lockdown); + idevice_free(device); + + return dev_mode_status; +} + +static int amfi_service_send_msg(property_list_service_client_t amfi, plist_t msg) +{ + int res; + property_list_service_error_t perr; + + perr = property_list_service_send_xml_plist(amfi, plist_copy(msg)); + if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) { + fprintf(stderr, "Could not send request to device: %d\n", perr); + res = 2; + } else { + plist_t reply = NULL; + perr = property_list_service_receive_plist(amfi, &reply); + if (perr == PROPERTY_LIST_SERVICE_E_SUCCESS) { + uint8_t success = 0; + plist_t val = plist_dict_get_item(reply, "Error"); + if (val) { + const char* err = plist_get_string_ptr(val, NULL); + fprintf(stderr, "Request failed: %s\n", err); + if (strstr(err, "passcode")) { + res = 2; + } else { + res = 1; + } + } else { + val = plist_dict_get_item(reply, "success"); + if (val) { + plist_get_bool_val(val, &success); + } + if (success) { + res = 0; + } else { + res = 1; + } + } + } else { + fprintf(stderr, "Could not receive reply from device: %d\n", perr); + res = 2; + } + plist_free(reply); + } + return res; +} + +static int amfi_send_action(idevice_t device, unsigned int action) +{ + lockdownd_client_t lockdown = NULL; + lockdownd_service_descriptor_t service = NULL; + lockdownd_error_t lerr; + + if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr); + return 1; + } + + lerr = lockdownd_start_service(lockdown, AMFI_LOCKDOWN_SERVICE_NAME, &service); + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "Could not start service %s: %s\nPlease note that this feature is only available on iOS 16+.\n", AMFI_LOCKDOWN_SERVICE_NAME, lockdownd_strerror(lerr)); + lockdownd_client_free(lockdown); + return 1; + } + lockdownd_client_free(lockdown); + lockdown = NULL; + + property_list_service_client_t amfi = NULL; + if (property_list_service_client_new(device, service, &amfi) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + fprintf(stderr, "Could not connect to %s on device\n", AMFI_LOCKDOWN_SERVICE_NAME); + if (service) + lockdownd_service_descriptor_free(service); + idevice_free(device); + return 1; + } + lockdownd_service_descriptor_free(service); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "action", plist_new_uint(action)); + + int result = amfi_service_send_msg(amfi, dict); + plist_free(dict); + + property_list_service_client_free(amfi); + amfi = NULL; + + return result; +} + +static int device_connected = 0; + +static void device_event_cb(const idevice_event_t* event, void* userdata) +{ + if (use_network && event->conn_type != CONNECTION_NETWORK) { + return; + } + if (!use_network && event->conn_type != CONNECTION_USBMUXD) { + return; + } + if (event->event == IDEVICE_DEVICE_ADD) { + if (!udid) { + udid = strdup(event->udid); + } + if (strcmp(udid, event->udid) == 0) { + device_connected = 1; + } + } else if (event->event == IDEVICE_DEVICE_REMOVE) { + if (strcmp(udid, event->udid) == 0) { + device_connected = 0; + } + } +} + + +#define WAIT_INTERVAL 200000 +#define WAIT_MAX(x) (x * (1000000 / WAIT_INTERVAL)) +#define WAIT_FOR(cond, timeout) { int __repeat = WAIT_MAX(timeout); while (!(cond) && __repeat-- > 0) { __usleep(WAIT_INTERVAL); } } + +int main(int argc, char *argv[]) +{ + idevice_t device = NULL; + idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + lockdownd_client_t lockdown = NULL; + lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR; + int res = 0; + int i; + int op = -1; + plist_t val = NULL; + + int c = 0; + const struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "udid", required_argument, NULL, 'u' }, + { "network", no_argument, NULL, 'n' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0} + }; + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + /* parse cmdline args */ + while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) { + switch (c) { + case 'd': + idevice_set_debug_level(1); + break; + case 'u': + if (!*optarg) { + fprintf(stderr, "ERROR: UDID argument must not be empty!\n"); + print_usage(argc, argv, 1); + return 2; + } + udid = optarg; + break; + case 'n': + use_network = 1; + break; + case 'h': + print_usage(argc, argv, 0); + return 0; + case 'v': + printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); + return 0; + default: + print_usage(argc, argv, 1); + return 2; + } + } + argc -= optind; + argv += optind; + + if (!argv[0]) { + fprintf(stderr, "ERROR: Missing command.\n"); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + i = 0; + if (!strcmp(argv[i], "list")) { + op = OP_LIST; + } + else if (!strcmp(argv[i], "enable")) { + op = OP_ENABLE; + } + else if (!strcmp(argv[i], "arm")) { + op = OP_ARM; + } + else if (!strcmp(argv[i], "confirm")) { + op = OP_CONFIRM; + } + else if (!strcmp(argv[i], "reveal")) { + op = OP_REVEAL; + } + + if ((op == -1) || (op >= NUM_OPS)) { + fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]); + print_usage(argc+optind, argv-optind, 1); + return 2; + } + + if (op == OP_LIST) { + idevice_info_t *dev_list = NULL; + + if (idevice_get_device_list_extended(&dev_list, &i) < 0) { + fprintf(stderr, "ERROR: Unable to retrieve device list!\n"); + return -1; + } + if (i > 0) { + printf("%-40s %s\n", "Device", "DeveloperMode"); + } + for (i = 0; dev_list[i] != NULL; i++) { + if (dev_list[i]->conn_type == CONNECTION_USBMUXD && use_network) continue; + if (dev_list[i]->conn_type == CONNECTION_NETWORK && !use_network) continue; + if (udid && (strcmp(dev_list[i]->udid, udid) != 0)) continue; + int mode = get_developer_mode_status(dev_list[i]->udid, use_network); + const char *mode_str = "N/A"; + if (mode == 1) { + mode_str = "enabled"; + } else if (mode == 0) { + mode_str = "disabled"; + } + printf("%-40s %s\n", dev_list[i]->udid, mode_str); + } + idevice_device_list_extended_free(dev_list); + + return 0; + } + + idevice_subscription_context_t context = NULL; + idevice_events_subscribe(&context, device_event_cb, NULL); + + WAIT_FOR(device_connected, 10); + + ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); + if (ret != IDEVICE_E_SUCCESS) { + if (udid) { + printf("No device found with udid %s.\n", udid); + } else { + printf("No device found.\n"); + } + return 1; + } + + if (!udid) { + idevice_get_udid(device, &udid); + } + + if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { + fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr); + idevice_free(device); + return 1; + } + + lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val); + lockdownd_client_free(lockdown); + lockdown = NULL; + if (lerr != LOCKDOWN_E_SUCCESS) { + fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr)); + idevice_free(device); + return 1; + } + + uint8_t dev_mode_status = 0; + plist_get_bool_val(val, &dev_mode_status); + + if ((op == OP_ENABLE || op == OP_ARM) && dev_mode_status) { + if (dev_mode_status) { + printf("DeveloperMode is already enabled.\n"); + return 0; + } + res = 0; + } else { + if (op == OP_ENABLE || op == OP_ARM) { + res = amfi_send_action(device, DEV_MODE_ARM); + if (res == 0) { + if (op == OP_ARM) { + printf("%s: Developer Mode armed, device will reboot now.\n", udid); + } else { + printf("%s: Developer Mode armed, waiting for reboot...\n", udid); + + // waiting for device to disconnect... + WAIT_FOR(!device_connected, 20); + + // waiting for device to reconnect... + WAIT_FOR(device_connected, 60); + + res = amfi_send_action(device, DEV_MODE_ENABLE); + if (res == 0) { + printf("%s: Developer Mode successfully enabled.\n", udid); + } else { + printf("%s: Failed to enable developer mode (%d)\n", udid, res); + } + } + } else if (res == 2) { + amfi_send_action(device, DEV_MODE_REVEAL); + printf("%s: Developer Mode could not be enabled because the device has a passcode set. You have to enable it on the device itself under Settings -> Privacy & Security -> Developer Mode.\n", udid); + } else { + printf("%s: Failed to arm Developer Mode (%d)\n", udid, res); + } + } else if (op == OP_CONFIRM) { + res = amfi_send_action(device, DEV_MODE_ENABLE); + if (res == 0) { + printf("%s: Developer Mode successfully enabled.\n", udid); + } else { + printf("%s: Failed to enable Developer Mode (%d)\n", udid, res); + } + } else if (op == OP_REVEAL) { + res = amfi_send_action(device, DEV_MODE_REVEAL); + if (res == 0) { + printf("%s: Developer Mode menu revealed successfully.\n", udid); + } else { + printf("%s: Failed to reveal Developer Mode menu (%d)\n", udid, res); + } + } + } + + idevice_free(device); + + return res; +} -- cgit v1.1-32-gdbae