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