| /* |
| * 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 <config.h> |
| #endif |
| |
| #define TOOL_NAME "idevicedevmodectl" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <getopt.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #ifndef WIN32 |
| #include <signal.h> |
| #endif |
| |
| #ifdef WIN32 |
| #include <windows.h> |
| #define __usleep(x) Sleep(x/1000) |
| #else |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #define __usleep(x) usleep(x) |
| #endif |
| |
| #include <libimobiledevice/libimobiledevice.h> |
| #include <libimobiledevice/lockdown.h> |
| #include <libimobiledevice/property_list_service.h> |
| #include <libimobiledevice-glue/utils.h> |
| |
| #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) { |
| 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 { |
| res = plist_dict_get_item(reply, "success") ? 0 : 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) { |
| idevice_free(device); |
| 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); |
| |
| do { |
| // waiting for device to disconnect... |
| idevice_free(device); |
| device = NULL; |
| WAIT_FOR(!device_connected, 40); |
| if (device_connected) { |
| printf("%s: ERROR: Device didn't reboot?!\n", udid); |
| res = 2; |
| break; |
| } |
| printf("disconnected\n"); |
| |
| // waiting for device to reconnect... |
| WAIT_FOR(device_connected, 60); |
| if (!device_connected) { |
| printf("%s: ERROR: Device didn't re-connect?!\n", udid); |
| res = 2; |
| break; |
| } |
| printf("connected\n"); |
| |
| idevice_new(&device, udid); |
| res = amfi_send_action(device, DEV_MODE_ENABLE); |
| } while (0); |
| 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; |
| } |