Skip to content

Instantly share code, notes, and snippets.

@nikias
Last active February 18, 2023 06:59
Show Gist options
  • Save nikias/262bd709c1651e0139eb9e3a2e2d33f4 to your computer and use it in GitHub Desktop.
Save nikias/262bd709c1651e0139eb9e3a2e2d33f4 to your computer and use it in GitHub Desktop.
idevicedevmodectl - compile in configured libimobiledevice source tree with cc -o idevicedevmodectl idevicedevmodectl.c -DHAVE_CONFIG_H -I. -I/usr/local/include -lplist-2.0 -limobiledevice-1.0
/*
* 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 <errno.h>
#ifndef WIN32
#include <signal.h>
#endif
#ifdef WIN32
#include <windows.h>
#define __usleep(x) Sleep(x/1000)
#else
#include <arpa/inet.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/disable developer mode on iOS 16+ devices or print the current status.\n"
"\n"
"Where COMMAND is one of:\n"
" list Prints the developer mode status of all connected devices\n"
" or for a specific one if --udid is given.\n"
" enable Enables developer mode (device will reboot),\n"
" and confirms it after device booted up again\n"
"\n"
" arm Arms the developer mode (device will reboot)\n"
" confirm Confirms enabling of developer mode\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 prints usage information\n"
" -v, --version prints version information\n"
"\n"
"Homepage: <" PACKAGE_URL ">\n"
"Bug Reports: <" PACKAGE_BUGREPORT ">\n"
);
}
enum {
OP_LIST,
OP_ENABLE,
OP_ARM,
OP_CONFIRM,
NUM_OPS
};
#define DEV_MODE_ARM 1
#define DEV_MODE_ENABLE 2
static int get_developer_mode_status(const char* 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, 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) {
char* err = NULL;
plist_get_string_val(val, &err);
fprintf(stderr, "Request failed: %s\n", err);
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;
int output_xml = 0;
char* udid = NULL;
int action = 0;
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;
}
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) {
int 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 {
printf("%s: Failed to arm Developer Mode (%d)\n", udid, res);
}
} else if (op == OP_CONFIRM) {
int 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);
}
}
}
idevice_free(device);
return res;
}
@Dantee296
Copy link

@truonggiang0710 as soon as device available via usbmux you can send action 2

@truonggiang0710
Copy link

Thanks @Dantee296

@nikias
Copy link
Author

nikias commented Nov 22, 2022

I think the "reveal toggle in settings" part is not needed anymore, at least from what I see. Anyway, I updated the code, feel free to test.

@nikias
Copy link
Author

nikias commented Nov 22, 2022

I also made it wait for reboot when doing the enable action, but also allowed the separate steps arm and confirm actions.

@mexmer
Copy link

mexmer commented Nov 22, 2022

I think the "reveal toggle in settings" part is not needed anymore, at least from what I see. Anyway, I updated the code, feel free to test.

@nikias
reveal is needed for passcode protected devices.
if developer mode is not enabled on passcode protected device, then option is not present in "privacy & security" menu until you do reveal ... it will stay there until next reboot
if you have developer mode enabled on passcode protected device and disable it, option will also disapear (you need to go out of "privacy & security" menu and then return, to "unsee" it)

just retested on ios 16.0 and 16.1.1

@nikias
Copy link
Author

nikias commented Nov 22, 2022

Interestingly on my 13 pro device (with passcode) it was always visible, and also didn't disappear when disabling developer mode.
I am using and older macOS version and old Xcode version (no iOS 15 or 16 support!) so it can't trigger this automatically when I plug it in. Really weird. I will have a look again.

@mexmer
Copy link

mexmer commented Nov 22, 2022

i have iPhone SE with iOS 16.0, and iPad Air 5 with iOS 16.1.1, connecting them either to windows or linux. no developer mode menu, until triggered by amfi with value 0.

do you use iOS beta by any chance?

@nikias
Copy link
Author

nikias commented Dec 10, 2022

@mexmer I pushed commit a6775bc588db13838bebec42b139748d337e7189 which adds the tool. I added an automatic reveal if the enabling fails due to a passcode being set, and also added a manual reveal command.

@mexmer
Copy link

mexmer commented Dec 12, 2022

@nikias thanks, just tested it, and works fine with ipad(16.1.2) and iphone (still 16.0.1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment