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;
}
@mexmer
Copy link

mexmer commented Aug 9, 2022

are you sure, this is working?
i have developer mode enabled but it says N/A

also i could not enable developer mode with it, i got back "password protected" message, but developer mode switch didn't appear in privacy options. i needed to connect my ipad to mac and run xcode, then it finally shown developer mode toggle, after enabling and restarting ipad, it did ask me to allow developer mode, and then it was enabled, but your tool says N/A

device is iPad Air 3 with latest beta

@nikias
Copy link
Author

nikias commented Aug 9, 2022

I am not sure if this works, I put this here for testing purposes to get some feedback. Basically I checked what the original devmodectl is doing and copied its behavior...

@mexmer
Copy link

mexmer commented Aug 10, 2022

ok, i will check further. just for the record, you did test it with device that has password/touchid set, or without it?

@liu6x6
Copy link

liu6x6 commented Aug 16, 2022

just have a test on my beta4 , my Mac is 12.4
like mexmer. the status is always N/A
if i am trying to disable the devloper mode. device is restart and it's says 'Could not disable DeveloperMode (unknown error)'
if i am trying to enable it. the message is 'Could not enable DeveloperMode: An unknown error has occured'

@mexmer
Copy link

mexmer commented Aug 16, 2022

i have usb capture (using totalphase) from mac->ipad communication, but i can't find anything in it, that suggests how it's enabled.
i noticed they sending Xcode as Label, and ProtocolVersion 2 (string value) with every query ... but that's about it, it just asks for GetValue, which returns prohibited ... but for some reason on ipad privacy menu developer mode toggle appeared ... i tried to simulate that, but without any success.

@mexmer
Copy link

mexmer commented Aug 17, 2022

just fyi, i'm testing this on linux and windows, next week i will have mac mini again, so i can test it on that. but on windows and linux it doesn't seem to work.

@mexmer
Copy link

mexmer commented Aug 17, 2022

also i wonder, what is the issue with the tool, should not both of this commands return same thing?

image

@mexmer
Copy link

mexmer commented Aug 17, 2022

nevermind, there is typo on line #224

should be either > 0 or == 1

@liu6x6
Copy link

liu6x6 commented Aug 18, 2022

compare the code from the pymobiledevice3. look like send {'action': 1} will restart the device, and then device will popup an alert. if send {'action': 2} then the developer mode will be opened.
and i think disable the develop mode will not work.

@mexmer
Copy link

mexmer commented Aug 18, 2022

@liu6x6 no idea what part of code or function of pymobiledevice3 you are talking about.

do you have code or link to code?

@mexmer
Copy link

mexmer commented Aug 18, 2022

@nikias, seems i figured it out.
from my testing

for "action"
if you send 0 it will enable developer mode item in privacy and security setting
if you send 1 it will either tell you "device is password protected" on device with passcode set, or it will cause device reset and then you get popup to enable developer mode on devices without passcode set
if you send 2 it returns error ... so i believe it's typo in your code

i have tested different values, but other than 1 and 0 give me error, so i'm not sure, if developer mode can be disabled other way than from setting

if you restart device with developer mode enabled, it will preserve developer mode after restart
if you disable developer mode, option to toggle developer mode is gone from setting after restart, and you need to either send 0(enable phone setting item) on locked phone or send 1 on unlocked phone

ios 16 version 20A5349b

@mexmer
Copy link

mexmer commented Aug 18, 2022

i suggest to add this tool into libimobile repo, after fixing few things

@liu6x6
Copy link

liu6x6 commented Aug 19, 2022

@mexmer
there pymobiledevice code is here https://github.com/doronz88/pymobiledevice3/blob/b4789e84ffec05f7915f290a932ec98d9671f74e/pymobiledevice3/services/amfi.py
according my test with my iOS 16 Beta6, look like send 0 is not working, nothing happend
sned 1 will restart the device and you will get a popup to eanble developer. if you sned 2 at this moment, the popup will disappear and it will enable the developer mode on the device.
i think the right flow is like this:
send 1 --> device restart and you will get a popup --> send 2 , pipup will disappear and develoer mode is enabled

@mexmer
Copy link

mexmer commented Aug 19, 2022

@liu6x6 normally you don't have "developer mode" menu in setting (under privacy)
only situation when that menu appears is - you already have enabled developer mode or you connect iphone/ipad to xcode.
by sending 0, this menu appears - in that python script is function
def create_amfi_show_override_path_file(self):
so author most likely figured out it's needed

sending 1 works only for phone without passcode enabled, on phones with passcode enabled this will throw "Error phone has passcode set", so you need to enable developer mode manually ... which you can't until you send 0

sending 2 gave me error, but i didn't try to use it, to confirm developer mode dialog after restart, code suggests it's for that

    def enable_developer_mode_post_restart(self):
        """ answer the prompt that appears after the restart with "yes"

i will test it, and let you know, but i suspect, it will again work only for devices without passcode set, because if you have passcode enabled, you need to confirm enabling developer mode with passcode.

@mexmer
Copy link

mexmer commented Aug 19, 2022

jus fyii, i'm talking about this
image
its on your ios device under Settings->Privacy & Security at bottom
if you don't have developer mode enabled, you don't see this.
if you send 0, this will show up in settings

on passcode protected devices, only option to enable developer mode is from phone settings

@Dantee296
Copy link

Dantee296 commented Aug 21, 2022

#define DEV_ACTION_REVEAL 0 // 0 = reveal toggle in settings
#define DEV_ACTION_ENABLE 1 // 1 = enable developer mode (only if no passcode is set)
#define DEV_ACTION_PROMPT 2 // 2 = answers developer mode enable prompt post-restart

if you just wanna reveal toggle in settings app just send action 0
with action 1 it ll enable and restart device
once device is back from restart we need to either send action 2 or manually accept to popup to enable developer mode

also DeveloperModeStatus works perfectly fine every time

@truonggiang0710
Copy link

@Dantee296 Can we send action 2 successfully after the booted is completed, or should we send it when the phone is booting?

@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