blob: bd1de6abc06adcab11fcc2460421e7a0a8ff405b [file] [log] [blame]
/*
* 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;
}