summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am16
-rw-r--r--tools/irecovery.c760
2 files changed, 776 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..15e2eb9
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,16 @@
1if BUILD_TOOLS
2AM_CPPFLAGS = -I$(top_srcdir)/include
3
4AM_CFLAGS = $(GLOBAL_CFLAGS) $(libusb_CFLAGS)
5AM_LDFLAGS = $(libusb_LIBS)
6
7bin_PROGRAMS = irecovery
8
9irecovery_SOURCES = irecovery.c
10irecovery_CFLAGS = $(AM_CFLAGS)
11irecovery_LDFLAGS = $(AM_LDFLAGS)
12if HAVE_READLINE
13irecovery_LDFLAGS += -lreadline
14endif
15irecovery_LDADD = $(top_builddir)/src/libirecovery-1.0.la
16endif
diff --git a/tools/irecovery.c b/tools/irecovery.c
new file mode 100644
index 0000000..b293324
--- /dev/null
+++ b/tools/irecovery.c
@@ -0,0 +1,760 @@
1/*
2 * irecovery.c
3 * Software frontend for iBoot/iBSS communication with iOS devices
4 *
5 * Copyright (c) 2012-2023 Nikias Bassen <nikias@gmx.li>
6 * Copyright (c) 2012-2015 Martin Szulecki <martin.szulecki@libimobiledevice.org>
7 * Copyright (c) 2010-2011 Chronic-Dev Team
8 * Copyright (c) 2010-2011 Joshua Hill
9 * Copyright (c) 2008-2011 Nicolas Haunold
10 *
11 * All rights reserved. This program and the accompanying materials
12 * are made available under the terms of the GNU Lesser General Public License
13 * (LGPL) version 2.1 which accompanies this distribution, and is available at
14 * http://www.gnu.org/licenses/lgpl-2.1.html
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
20 */
21
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
25
26#define TOOL_NAME "irecovery"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <unistd.h>
31#include <string.h>
32#include <getopt.h>
33#include <inttypes.h>
34#include <ctype.h>
35#include <libirecovery.h>
36#ifdef HAVE_READLINE
37#include <readline/readline.h>
38#include <readline/history.h>
39#else
40#ifndef _WIN32
41#include <termios.h>
42#endif
43#endif
44
45#ifdef _WIN32
46#include <windows.h>
47#include <conio.h>
48#define sleep(n) Sleep(1000 * n)
49#endif
50
51#define FILE_HISTORY_PATH ".irecovery"
52#define debug(...) if (verbose) fprintf(stderr, __VA_ARGS__)
53
54enum {
55 kNoAction,
56 kResetDevice,
57 kStartShell,
58 kSendCommand,
59 kSendFile,
60 kSendExploit,
61 kSendScript,
62 kShowMode,
63 kRebootToNormalMode,
64 kQueryInfo,
65 kListDevices
66};
67
68static unsigned int quit = 0;
69static unsigned int verbose = 0;
70
71void print_progress_bar(double progress);
72int received_cb(irecv_client_t client, const irecv_event_t* event);
73int progress_cb(irecv_client_t client, const irecv_event_t* event);
74int precommand_cb(irecv_client_t client, const irecv_event_t* event);
75int postcommand_cb(irecv_client_t client, const irecv_event_t* event);
76
77static void shell_usage()
78{
79 printf("Usage:\n");
80 printf(" /upload FILE\t\tsend FILE to device\n");
81 printf(" /limera1n [FILE]\trun limera1n exploit and send optional payload from FILE\n");
82 printf(" /deviceinfo\t\tprint device information (ECID, IMEI, etc.)\n");
83 printf(" /help\t\t\tshow this help\n");
84 printf(" /exit\t\t\texit interactive shell\n");
85}
86
87static const char* mode_to_str(int mode)
88{
89 switch (mode) {
90 case IRECV_K_RECOVERY_MODE_1:
91 case IRECV_K_RECOVERY_MODE_2:
92 case IRECV_K_RECOVERY_MODE_3:
93 case IRECV_K_RECOVERY_MODE_4:
94 return "Recovery";
95 break;
96 case IRECV_K_DFU_MODE:
97 return "DFU";
98 break;
99 case IRECV_K_PORT_DFU_MODE:
100 return "Port DFU";
101 break;
102 case IRECV_K_WTF_MODE:
103 return "WTF";
104 break;
105 default:
106 return "Unknown";
107 break;
108 }
109}
110
111static void buffer_read_from_filename(const char *filename, char **buffer, uint64_t *length)
112{
113 FILE *f;
114 uint64_t size;
115
116 *length = 0;
117
118 f = fopen(filename, "rb");
119 if (!f) {
120 return;
121 }
122
123 fseek(f, 0, SEEK_END);
124 size = ftell(f);
125 rewind(f);
126
127 if (size == 0) {
128 fclose(f);
129 return;
130 }
131
132 *buffer = (char*)malloc(sizeof(char)*(size+1));
133 fread(*buffer, sizeof(char), size, f);
134 fclose(f);
135
136 *length = size;
137}
138
139static void print_hex(unsigned char *buf, size_t len)
140{
141 size_t i;
142 for (i = 0; i < len; i++) {
143 printf("%02x", buf[i]);
144 }
145}
146
147static void print_device_info(irecv_client_t client)
148{
149 int ret, mode;
150 irecv_device_t device = NULL;
151 const struct irecv_device_info *devinfo = irecv_get_device_info(client);
152 if (devinfo) {
153 printf("CPID: 0x%04x\n", devinfo->cpid);
154 printf("CPRV: 0x%02x\n", devinfo->cprv);
155 printf("BDID: 0x%02x\n", devinfo->bdid);
156 printf("ECID: 0x%016" PRIx64 "\n", devinfo->ecid);
157 printf("CPFM: 0x%02x\n", devinfo->cpfm);
158 printf("SCEP: 0x%02x\n", devinfo->scep);
159 printf("IBFL: 0x%02x\n", devinfo->ibfl);
160 printf("SRTG: %s\n", (devinfo->srtg) ? devinfo->srtg : "N/A");
161 printf("SRNM: %s\n", (devinfo->srnm) ? devinfo->srnm : "N/A");
162 printf("IMEI: %s\n", (devinfo->imei) ? devinfo->imei : "N/A");
163 printf("NONC: ");
164 if (devinfo->ap_nonce) {
165 print_hex(devinfo->ap_nonce, devinfo->ap_nonce_size);
166 } else {
167 printf("N/A");
168 }
169 printf("\n");
170 printf("SNON: ");
171 if (devinfo->sep_nonce) {
172 print_hex(devinfo->sep_nonce, devinfo->sep_nonce_size);
173 } else {
174 printf("N/A");
175 }
176 printf("\n");
177 char* p = strstr(devinfo->serial_string, "PWND:[");
178 if (p) {
179 p+=6;
180 char* pend = strchr(p, ']');
181 if (pend) {
182 printf("PWND: %.*s\n", (int)(pend-p), p);
183 }
184 }
185 } else {
186 printf("Could not get device info?!\n");
187 }
188
189 ret = irecv_get_mode(client, &mode);
190 if (ret == IRECV_E_SUCCESS) {
191 switch (devinfo->pid) {
192 case 0x1881:
193 printf("MODE: DFU via Debug USB (KIS)\n");
194 break;
195 default:
196 printf("MODE: %s\n", mode_to_str(mode));
197 break;
198 }
199 }
200
201 irecv_devices_get_device_by_client(client, &device);
202 if (device) {
203 printf("PRODUCT: %s\n", device->product_type);
204 printf("MODEL: %s\n", device->hardware_model);
205 printf("NAME: %s\n", device->display_name);
206 }
207}
208
209static void print_devices()
210{
211 struct irecv_device *devices = irecv_devices_get_all();
212 struct irecv_device *device = NULL;
213 int i = 0;
214
215 for (i = 0; devices[i].product_type != NULL; i++) {
216 device = &devices[i];
217
218 printf("%s %s 0x%02x 0x%04x %s\n", device->product_type, device->hardware_model, device->board_id, device->chip_id, device->display_name);
219 }
220}
221
222static int _is_breq_command(const char* cmd)
223{
224 return (
225 !strcmp(cmd, "go")
226 || !strcmp(cmd, "bootx")
227 || !strcmp(cmd, "reboot")
228 || !strcmp(cmd, "memboot")
229 );
230}
231
232static void parse_command(irecv_client_t client, unsigned char* command, unsigned int size)
233{
234 char* cmd = strdup((char*)command);
235 char* action = strtok(cmd, " ");
236
237 if (!strcmp(cmd, "/exit")) {
238 quit = 1;
239 } else if (!strcmp(cmd, "/help")) {
240 shell_usage();
241 } else if (!strcmp(cmd, "/upload")) {
242 char* filename = strtok(NULL, " ");
243 debug("Uploading file %s\n", filename);
244 if (filename != NULL) {
245 irecv_send_file(client, filename, 0);
246 }
247 } else if (!strcmp(cmd, "/deviceinfo")) {
248 print_device_info(client);
249 } else if (!strcmp(cmd, "/limera1n")) {
250 char* filename = strtok(NULL, " ");
251 debug("Sending limera1n payload %s\n", filename);
252 if (filename != NULL) {
253 irecv_send_file(client, filename, 0);
254 }
255 irecv_trigger_limera1n_exploit(client);
256 } else if (!strcmp(cmd, "/execute")) {
257 char* filename = strtok(NULL, " ");
258 debug("Executing script %s\n", filename);
259 if (filename != NULL) {
260 char* buffer = NULL;
261 uint64_t buffer_length = 0;
262 buffer_read_from_filename(filename, &buffer, &buffer_length);
263 if (buffer) {
264 buffer[buffer_length] = '\0';
265 irecv_execute_script(client, buffer);
266 free(buffer);
267 } else {
268 printf("Could not read file '%s'\n", filename);
269 }
270 }
271 } else {
272 printf("Unsupported command %s. Use /help to get a list of available commands.\n", cmd);
273 }
274
275 free(action);
276}
277
278static void load_command_history()
279{
280#ifdef HAVE_READLINE
281 read_history(FILE_HISTORY_PATH);
282#endif
283}
284
285static void append_command_to_history(const char* cmd)
286{
287#ifdef HAVE_READLINE
288 add_history(cmd);
289 write_history(FILE_HISTORY_PATH);
290#endif
291}
292
293#ifndef HAVE_READLINE
294#ifdef _WIN32
295#define BS_CC '\b'
296#else
297#define BS_CC 0x7f
298#define getch getchar
299#endif
300static void get_input(char *buf, int maxlen)
301{
302 int len = 0;
303 int c;
304
305 while ((c = getch())) {
306 if ((c == '\r') || (c == '\n')) {
307 break;
308 }
309 if (isprint(c)) {
310 if (len < maxlen-1)
311 buf[len++] = c;
312 } else if (c == BS_CC) {
313 if (len > 0) {
314 fputs("\b \b", stdout);
315 len--;
316 }
317 }
318 }
319 buf[len] = 0;
320}
321#endif
322
323static void init_shell(irecv_client_t client)
324{
325 irecv_error_t error = 0;
326 load_command_history();
327 irecv_event_subscribe(client, IRECV_PROGRESS, &progress_cb, NULL);
328 irecv_event_subscribe(client, IRECV_RECEIVED, &received_cb, NULL);
329 irecv_event_subscribe(client, IRECV_PRECOMMAND, &precommand_cb, NULL);
330 irecv_event_subscribe(client, IRECV_POSTCOMMAND, &postcommand_cb, NULL);
331 while (!quit) {
332 error = irecv_receive(client);
333 if (error != IRECV_E_SUCCESS) {
334 debug("%s\n", irecv_strerror(error));
335 break;
336 }
337#ifdef HAVE_READLINE
338 char* cmd = readline("> ");
339#else
340 char cmdbuf[4096];
341 const char* cmd = &cmdbuf[0];
342 printf("> ");
343 fflush(stdout);
344 get_input(cmdbuf, sizeof(cmdbuf));
345#endif
346 if (cmd && *cmd) {
347 if (_is_breq_command(cmd)) {
348 error = irecv_send_command_breq(client, cmd, 1);
349 } else {
350 error = irecv_send_command(client, cmd);
351 }
352 if (error != IRECV_E_SUCCESS) {
353 quit = 1;
354 }
355
356 append_command_to_history(cmd);
357 }
358#ifdef HAVE_READLINE
359 free(cmd);
360#endif
361 }
362}
363
364int received_cb(irecv_client_t client, const irecv_event_t* event)
365{
366 if (event->type == IRECV_RECEIVED) {
367 int i = 0;
368 int size = event->size;
369 const char* data = event->data;
370 for (i = 0; i < size; i++) {
371 printf("%c", data[i]);
372 }
373 }
374
375 return 0;
376}
377
378int precommand_cb(irecv_client_t client, const irecv_event_t* event)
379{
380 if (event->type == IRECV_PRECOMMAND) {
381 if (event->data[0] == '/') {
382 parse_command(client, (unsigned char*)event->data, event->size);
383 return -1;
384 }
385 }
386
387 return 0;
388}
389
390int postcommand_cb(irecv_client_t client, const irecv_event_t* event)
391{
392 char* value = NULL;
393 char* action = NULL;
394 char* command = NULL;
395 char* argument = NULL;
396 irecv_error_t error = IRECV_E_SUCCESS;
397
398 if (event->type == IRECV_POSTCOMMAND) {
399 command = strdup(event->data);
400 action = strtok(command, " ");
401 if (!strcmp(action, "getenv")) {
402 argument = strtok(NULL, " ");
403 error = irecv_getenv(client, argument, &value);
404 if (error != IRECV_E_SUCCESS) {
405 debug("%s\n", irecv_strerror(error));
406 free(command);
407 return error;
408 }
409 printf("%s\n", value);
410 free(value);
411 }
412
413 if (!strcmp(action, "reboot")) {
414 quit = 1;
415 }
416 }
417
418 free(command);
419
420 return 0;
421}
422
423int progress_cb(irecv_client_t client, const irecv_event_t* event)
424{
425 if (event->type == IRECV_PROGRESS) {
426 print_progress_bar(event->progress);
427 }
428
429 return 0;
430}
431
432void print_progress_bar(double progress)
433{
434 int i = 0;
435
436 if (progress < 0) {
437 return;
438 }
439
440 if (progress > 100) {
441 progress = 100;
442 }
443
444 printf("\r[");
445
446 for (i = 0; i < 50; i++) {
447 if (i < progress / 2) {
448 printf("=");
449 } else {
450 printf(" ");
451 }
452 }
453
454 printf("] %3.1f%%", progress);
455
456 fflush(stdout);
457
458 if (progress == 100) {
459 printf("\n");
460 }
461}
462
463static void print_usage(int argc, char **argv)
464{
465 char *name = NULL;
466 name = strrchr(argv[0], '/');
467 printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
468 printf("\n");
469 printf("Interact with an iOS device in DFU or recovery mode.\n");
470 printf("\n");
471 printf("OPTIONS:\n");
472 printf(" -i, --ecid ECID\tconnect to specific device by its ECID\n");
473 printf(" -c, --command CMD\trun CMD on device\n");
474 printf(" -m, --mode\t\tprint current device mode\n");
475 printf(" -f, --file FILE\tsend file to device\n");
476 printf(" -k, --payload FILE\tsend limera1n usb exploit payload from FILE\n");
477 printf(" -r, --reset\t\treset client\n");
478 printf(" -n, --normal\t\treboot device into normal mode (exit recovery loop)\n");
479 printf(" -e, --script FILE\texecutes recovery script from FILE\n");
480 printf(" -s, --shell\t\tstart an interactive shell\n");
481 printf(" -q, --query\t\tquery device info\n");
482 printf(" -a, --devices\t\tlist information for all known devices\n");
483 printf(" -v, --verbose\t\tenable verbose output, repeat for higher verbosity\n");
484 printf(" -h, --help\t\tprints this usage information\n");
485 printf(" -V, --version\t\tprints version information\n");
486 printf("\n");
487 printf("Homepage: <" PACKAGE_URL ">\n");
488 printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n");
489}
490
491int main(int argc, char* argv[])
492{
493 static struct option longopts[] = {
494 { "ecid", required_argument, NULL, 'i' },
495 { "command", required_argument, NULL, 'c' },
496 { "mode", no_argument, NULL, 'm' },
497 { "file", required_argument, NULL, 'f' },
498 { "payload", required_argument, NULL, 'k' },
499 { "reset", no_argument, NULL, 'r' },
500 { "normal", no_argument, NULL, 'n' },
501 { "script", required_argument, NULL, 'e' },
502 { "shell", no_argument, NULL, 's' },
503 { "query", no_argument, NULL, 'q' },
504 { "devices", no_argument, NULL, 'a' },
505 { "verbose", no_argument, NULL, 'v' },
506 { "help", no_argument, NULL, 'h' },
507 { "version", no_argument, NULL, 'V' },
508 { NULL, 0, NULL, 0 }
509 };
510 int i = 0;
511 int opt = 0;
512 int action = kNoAction;
513 uint64_t ecid = 0;
514 int mode = -1;
515 char* argument = NULL;
516 irecv_error_t error = 0;
517
518 char* buffer = NULL;
519 uint64_t buffer_length = 0;
520
521 if (argc == 1) {
522 print_usage(argc, argv);
523 return 0;
524 }
525
526 while ((opt = getopt_long(argc, argv, "i:vVhrsmnc:f:e:k:qa", longopts, NULL)) > 0) {
527 switch (opt) {
528 case 'i':
529 if (optarg) {
530 char* tail = NULL;
531 ecid = strtoull(optarg, &tail, 0);
532 if (tail && (tail[0] != '\0')) {
533 ecid = 0;
534 }
535 if (ecid == 0) {
536 fprintf(stderr, "ERROR: Could not parse ECID from argument '%s'\n", optarg);
537 return -1;
538 }
539 }
540 break;
541
542 case 'v':
543 verbose += 1;
544 break;
545
546 case 'h':
547 print_usage(argc, argv);
548 return 0;
549
550 case 'm':
551 action = kShowMode;
552 break;
553
554 case 'n':
555 action = kRebootToNormalMode;
556 break;
557
558 case 'r':
559 action = kResetDevice;
560 break;
561
562 case 's':
563 action = kStartShell;
564 break;
565
566 case 'f':
567 action = kSendFile;
568 argument = optarg;
569 break;
570
571 case 'c':
572 action = kSendCommand;
573 argument = optarg;
574 break;
575
576 case 'k':
577 action = kSendExploit;
578 argument = optarg;
579 break;
580
581 case 'e':
582 action = kSendScript;
583 argument = optarg;
584 break;
585
586 case 'q':
587 action = kQueryInfo;
588 break;
589
590 case 'a':
591 action = kListDevices;
592 print_devices();
593 return 0;
594
595 case 'V':
596 printf("%s %s", TOOL_NAME, PACKAGE_VERSION);
597#ifdef HAVE_READLINE
598 printf(" (readline)");
599#endif
600 printf("\n");
601 return 0;
602
603 default:
604 fprintf(stderr, "Unknown argument\n");
605 return -1;
606 }
607 }
608
609 if (action == kNoAction) {
610 fprintf(stderr, "ERROR: Missing action option\n");
611 print_usage(argc, argv);
612 return -1;
613 }
614
615 if (verbose)
616 irecv_set_debug_level(verbose);
617
618 irecv_client_t client = NULL;
619 for (i = 0; i <= 5; i++) {
620 debug("Attempting to connect... \n");
621
622 irecv_error_t err = irecv_open_with_ecid(&client, ecid);
623 if (err == IRECV_E_UNSUPPORTED) {
624 fprintf(stderr, "ERROR: %s\n", irecv_strerror(err));
625 return -1;
626 }
627 else if (err != IRECV_E_SUCCESS)
628 sleep(1);
629 else
630 break;
631
632 if (i == 5) {
633 fprintf(stderr, "ERROR: %s\n", irecv_strerror(err));
634 return -1;
635 }
636 }
637
638 irecv_device_t device = NULL;
639 irecv_devices_get_device_by_client(client, &device);
640 if (device)
641 debug("Connected to %s, model %s, cpid 0x%04x, bdid 0x%02x\n", device->product_type, device->hardware_model, device->chip_id, device->board_id);
642
643 const struct irecv_device_info *devinfo = irecv_get_device_info(client);
644
645 switch (action) {
646 case kResetDevice:
647 irecv_reset(client);
648 break;
649
650 case kSendFile:
651 irecv_event_subscribe(client, IRECV_PROGRESS, &progress_cb, NULL);
652 error = irecv_send_file(client, argument, IRECV_SEND_OPT_DFU_NOTIFY_FINISH);
653 debug("%s\n", irecv_strerror(error));
654 break;
655
656 case kSendCommand:
657 if (devinfo->pid == 0x1881) {
658 printf("Shell is not available in Debug USB (KIS) mode.\n");
659 break;
660 }
661 if (_is_breq_command(argument)) {
662 error = irecv_send_command_breq(client, argument, 1);
663 } else {
664 error = irecv_send_command(client, argument);
665 }
666 debug("%s\n", irecv_strerror(error));
667 break;
668
669 case kSendExploit:
670 if (devinfo->pid == 0x1881) {
671 printf("Shell is not available in Debug USB (KIS) mode.\n");
672 break;
673 }
674 if (argument != NULL) {
675 irecv_event_subscribe(client, IRECV_PROGRESS, &progress_cb, NULL);
676 error = irecv_send_file(client, argument, 0);
677 if (error != IRECV_E_SUCCESS) {
678 debug("%s\n", irecv_strerror(error));
679 break;
680 }
681 }
682 error = irecv_trigger_limera1n_exploit(client);
683 debug("%s\n", irecv_strerror(error));
684 break;
685
686 case kStartShell:
687 if (devinfo->pid == 0x1881) {
688 printf("This feature is not supported in Debug USB (KIS) mode.\n");
689 break;
690 }
691 init_shell(client);
692 break;
693
694 case kSendScript:
695 if (devinfo->pid == 0x1881) {
696 printf("This feature is not supported in Debug USB (KIS) mode.\n");
697 break;
698 }
699 buffer_read_from_filename(argument, &buffer, &buffer_length);
700 if (buffer) {
701 buffer[buffer_length] = '\0';
702
703 error = irecv_execute_script(client, buffer);
704 if (error != IRECV_E_SUCCESS) {
705 debug("%s\n", irecv_strerror(error));
706 }
707
708 free(buffer);
709 } else {
710 fprintf(stderr, "Could not read file '%s'\n", argument);
711 }
712 break;
713
714 case kShowMode: {
715 irecv_get_mode(client, &mode);
716 printf("%s Mode", mode_to_str(mode));
717 if (devinfo->pid == 0x1881) {
718 printf(" via Debug USB (KIS)");
719 }
720 printf("\n");
721 break;
722 }
723 case kRebootToNormalMode:
724 if (devinfo->pid == 0x1881) {
725 printf("This feature is not supported in Debug USB (KIS) mode.\n");
726 break;
727 }
728 error = irecv_setenv(client, "auto-boot", "true");
729 if (error != IRECV_E_SUCCESS) {
730 debug("%s\n", irecv_strerror(error));
731 break;
732 }
733
734 error = irecv_saveenv(client);
735 if (error != IRECV_E_SUCCESS) {
736 debug("%s\n", irecv_strerror(error));
737 break;
738 }
739
740 error = irecv_reboot(client);
741 if (error != IRECV_E_SUCCESS) {
742 debug("%s\n", irecv_strerror(error));
743 } else {
744 debug("%s\n", irecv_strerror(error));
745 }
746 break;
747
748 case kQueryInfo:
749 print_device_info(client);
750 break;
751
752 default:
753 fprintf(stderr, "Unknown action\n");
754 break;
755 }
756
757 irecv_close(client);
758
759 return 0;
760}