/* * loki_patch * * A utility to patch unsigned boot and recovery images to make * them suitable for booting on the AT&T/Verizon Samsung * Galaxy S4, Galaxy Stellar, and various locked LG devices * * by Dan Rosenberg (@djrbliss) * */ #include #include #include #include #include #include #include #include "loki.h" struct target { char *vendor; char *device; char *build; unsigned long check_sigs; unsigned long hdr; int lg; }; struct target targets[] = { { .vendor = "AT&T", .device = "Samsung Galaxy S4", .build = "JDQ39.I337UCUAMDB or JDQ39.I337UCUAMDL", .check_sigs = 0x88e0ff98, .hdr = 0x88f3bafc, .lg = 0, }, { .vendor = "Verizon", .device = "Samsung Galaxy S4", .build = "JDQ39.I545VRUAMDK", .check_sigs = 0x88e0fe98, .hdr = 0x88f372fc, .lg = 0, }, { .vendor = "DoCoMo", .device = "Samsung Galaxy S4", .build = "JDQ39.SC04EOMUAMDI", .check_sigs = 0x88e0fcd8, .hdr = 0x88f0b2fc, .lg = 0, }, { .vendor = "Verizon", .device = "Samsung Galaxy Stellar", .build = "IMM76D.I200VRALH2", .check_sigs = 0x88e0f5c0, .hdr = 0x88ed32e0, .lg = 0, }, { .vendor = "Verizon", .device = "Samsung Galaxy Stellar", .build = "JZO54K.I200VRBMA1", .check_sigs = 0x88e101ac, .hdr = 0x88ed72e0, .lg = 0, }, { .vendor = "T-Mobile", .device = "LG Optimus F3Q", .build = "D52010c", .check_sigs = 0x88f1079c, .hdr = 0x88f64508, .lg = 1, }, { .vendor = "DoCoMo", .device = "LG Optimus G", .build = "L01E20b", .check_sigs = 0x88F10E48, .hdr = 0x88F54418, .lg = 1, }, { .vendor = "DoCoMo", .device = "LG Optimus it L05E", .build = "L05E10d", .check_sigs = 0x88f1157c, .hdr = 0x88f31e10, .lg = 1, }, { .vendor = "DoCoMo", .device = "LG Optimus G Pro", .build = "L04E10f", .check_sigs = 0x88f1102c, .hdr = 0x88f54418, .lg = 1, }, { .vendor = "AT&T or HK", .device = "LG Optimus G Pro", .build = "E98010g or E98810b", .check_sigs = 0x88f11084, .hdr = 0x88f54418, .lg = 1, }, { .vendor = "KT, LGU, or SKT", .device = "LG Optimus G Pro", .build = "F240K10o, F240L10v, or F240S10w", .check_sigs = 0x88f110b8, .hdr = 0x88f54418, .lg = 1, }, { .vendor = "KT, LGU, or SKT", .device = "LG Optimus LTE 2", .build = "F160K20g, F160L20f, F160LV20d, or F160S20f", .check_sigs = 0x88f10864, .hdr = 0x88f802b8, .lg = 1, }, { .vendor = "MetroPCS", .device = "LG Spirit", .build = "MS87010a_05", .check_sigs = 0x88f0e634, .hdr = 0x88f68194, .lg = 1, }, { .vendor = "MetroPCS", .device = "LG Motion", .build = "MS77010f_01", .check_sigs = 0x88f1015c, .hdr = 0x88f58194, .lg = 1, }, { .vendor = "Verizon", .device = "LG Lucid 2", .build = "VS87010B_12", .check_sigs = 0x88f10adc, .hdr = 0x88f702bc, .lg = 1, }, { .vendor = "Verizon", .device = "LG Spectrum 2", .build = "VS93021B_05", .check_sigs = 0x88f10c10, .hdr = 0x88f84514, .lg = 1, }, { .vendor = "Boost Mobile", .device = "LG Optimus F7", .build = "LG870ZV4_06", .check_sigs = 0x88f11714, .hdr = 0x88f842ac, .lg = 1, }, { .vendor = "US Cellular", .device = "LG Optimus F7", .build = "US78011a", .check_sigs = 0x88f112c8, .hdr = 0x88f84518, .lg = 1, }, { .vendor = "Sprint", .device = "LG Optimus F7", .build = "LG870ZV5_02", .check_sigs = 0x88f11710, .hdr = 0x88f842a8, .lg = 1, }, { .vendor = "Virgin Mobile", .device = "LG Optimus F3", .build = "LS720ZV5", .check_sigs = 0x88f108f0, .hdr = 0x88f854f4, .lg = 1, }, { .vendor = "T-Mobile and MetroPCS", .device = "LG Optimus F3", .build = "LS720ZV5", .check_sigs = 0x88f10264, .hdr = 0x88f64508, .lg = 1, }, { .vendor = "AT&T", .device = "LG G2", .build = "D80010d", .check_sigs = 0xf8132ac, .hdr = 0xf906440, .lg = 1, }, { .vendor = "Verizon", .device = "LG G2", .build = "VS98010b", .check_sigs = 0xf8131f0, .hdr = 0xf906440, .lg = 1, }, { .vendor = "AT&T", .device = "LG G2", .build = "D80010o", .check_sigs = 0xf813428, .hdr = 0xf904400, .lg = 1, }, { .vendor = "Verizon", .device = "LG G2", .build = "VS98012b", .check_sigs = 0xf813210, .hdr = 0xf906440, .lg = 1, }, { .vendor = "T-Mobile or Canada", .device = "LG G2", .build = "D80110c or D803", .check_sigs = 0xf813294, .hdr = 0xf906440, .lg = 1, }, { .vendor = "International", .device = "LG G2", .build = "D802b", .check_sigs = 0xf813a70, .hdr = 0xf9041c0, .lg = 1, }, { .vendor = "Sprint", .device = "LG G2", .build = "LS980ZV7", .check_sigs = 0xf813460, .hdr = 0xf9041c0, .lg = 1, }, { .vendor = "KT or LGU", .device = "LG G2", .build = "F320K, F320L", .check_sigs = 0xf81346c, .hdr = 0xf8de440, .lg = 1, }, { .vendor = "SKT", .device = "LG G2", .build = "F320S", .check_sigs = 0xf8132e4, .hdr = 0xf8ee440, .lg = 1, }, { .vendor = "SKT", .device = "LG G2", .build = "F320S11c", .check_sigs = 0xf813470, .hdr = 0xf8de440, .lg = 1, }, { .vendor = "DoCoMo", .device = "LG G2", .build = "L-01F", .check_sigs = 0xf813538, .hdr = 0xf8d41c0, .lg = 1, }, { .vendor = "KT", .device = "LG G Flex", .build = "F340K", .check_sigs = 0xf8124a4, .hdr = 0xf8b6440, .lg = 1, }, { .vendor = "KDDI", .device = "LG G Flex", .build = "LGL2310d", .check_sigs = 0xf81261c, .hdr = 0xf8b41c0, .lg = 1, }, { .vendor = "International", .device = "LG Optimus F5", .build = "P87510e", .check_sigs = 0x88f10a9c, .hdr = 0x88f702b8, .lg = 1, }, { .vendor = "SKT", .device = "LG Optimus LTE 3", .build = "F260S10l", .check_sigs = 0x88f11398, .hdr = 0x88f8451c, .lg = 1, }, { .vendor = "International", .device = "LG G Pad 8.3", .build = "V50010a", .check_sigs = 0x88f10814, .hdr = 0x88f801b8, .lg = 1, }, { .vendor = "International", .device = "LG G Pad 8.3", .build = "V50010c or V50010e", .check_sigs = 0x88f108bc, .hdr = 0x88f801b8, .lg = 1, }, { .vendor = "Verizon", .device = "LG G Pad 8.3", .build = "VK81010c", .check_sigs = 0x88f11080, .hdr = 0x88fd81b8, .lg = 1, }, { .vendor = "International", .device = "LG Optimus L9 II", .build = "D60510a", .check_sigs = 0x88f10d98, .hdr = 0x88f84aa4, .lg = 1, }, { .vendor = "MetroPCS", .device = "LG Optimus F6", .build = "MS50010e", .check_sigs = 0x88f10260, .hdr = 0x88f70508, .lg = 1, }, { .vendor = "Open EU", .device = "LG Optimus F6", .build = "D50510a", .check_sigs = 0x88f10284, .hdr = 0x88f70aa4, .lg = 1, }, { .vendor = "KDDI", .device = "LG Isai", .build = "LGL22", .check_sigs = 0xf813458, .hdr = 0xf8d41c0, .lg = 1, }, { .vendor = "KDDI", .device = "LG", .build = "LGL21", .check_sigs = 0x88f10218, .hdr = 0x88f50198, .lg = 1, }, { .vendor = "KT", .device = "LG Optimus GK", .build = "F220K", .check_sigs = 0x88f11034, .hdr = 0x88f54418, .lg = 1, }, { .vendor = "International", .device = "LG Vu 3", .build = "F300L", .check_sigs = 0xf813170, .hdr = 0xf8d2440, .lg = 1, }, { .vendor = "Sprint", .device = "LG Viper", .build = "LS840ZVK", .check_sigs = 0x4010fe18, .hdr = 0x40194198, .lg = 1, }, { .vendor = "International", .device = "LG G Flex", .build = "D95510a", .check_sigs = 0xf812490, .hdr = 0xf8c2440, .lg = 1, }, { .vendor = "Sprint", .device = "LG Mach", .build = "LS860ZV7", .check_sigs = 0x88f102b4, .hdr = 0x88f6c194, .lg = 1, }, }; static unsigned char patch[] = PATCH; int patch_shellcode(unsigned int header, unsigned int ramdisk) { unsigned int i; int found_header, found_ramdisk; unsigned int *ptr; found_header = 0; found_ramdisk = 0; for (i = 0; i < sizeof(patch); i++) { ptr = (unsigned int *)&patch[i]; if (*ptr == 0xffffffff) { *ptr = header; found_header = 1; } if (*ptr == 0xeeeeeeee) { *ptr = ramdisk; found_ramdisk = 1; } } if (found_header && found_ramdisk) return 0; return -1; } int loki_patch(const char* partition_label, const char* aboot_image, const char* in_image, const char* out_image) { int ifd, ofd, aboot_fd, pos, i, recovery, offset, fake_size; unsigned int orig_ramdisk_size, orig_kernel_size, page_kernel_size, page_ramdisk_size, page_size, page_mask; unsigned long target, aboot_base; void *orig, *aboot, *ptr; struct target *tgt; struct stat st; struct boot_img_hdr *hdr; struct loki_hdr *loki_hdr; char *buf; if (!strcmp(partition_label, "boot")) { recovery = 0; } else if (!strcmp(partition_label, "recovery")) { recovery = 1; } else { printf("[+] First argument must be \"boot\" or \"recovery\".\n"); return 1; } /* Open input files */ aboot_fd = open(aboot_image, O_RDONLY); if (aboot_fd < 0) { printf("[-] Failed to open %s for reading.\n", aboot_image); return 1; } ifd = open(in_image, O_RDONLY); if (ifd < 0) { printf("[-] Failed to open %s for reading.\n", in_image); return 1; } ofd = open(out_image, O_WRONLY|O_CREAT|O_TRUNC, 0644); if (ofd < 0) { printf("[-] Failed to open %s for writing.\n", out_image); return 1; } /* Find the signature checking function via pattern matching */ if (fstat(aboot_fd, &st)) { printf("[-] fstat() failed.\n"); return 1; } aboot = mmap(0, (st.st_size + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, aboot_fd, 0); if (aboot == MAP_FAILED) { printf("[-] Failed to mmap aboot.\n"); return 1; } target = 0; aboot_base = *(unsigned int *)(aboot + 12) - 0x28; for (ptr = aboot; ptr < aboot + st.st_size - 0x1000; ptr++) { if (!memcmp(ptr, PATTERN1, 8) || !memcmp(ptr, PATTERN2, 8) || !memcmp(ptr, PATTERN3, 8) || !memcmp(ptr, PATTERN4, 8) || !memcmp(ptr, PATTERN5, 8)) { target = (unsigned long)ptr - (unsigned long)aboot + aboot_base; break; } } /* Do a second pass for the second LG pattern. This is necessary because * apparently some LG models have both LG patterns, which throws off the * fingerprinting. */ if (!target) { for (ptr = aboot; ptr < aboot + st.st_size - 0x1000; ptr++) { if (!memcmp(ptr, PATTERN6, 8)) { target = (unsigned long)ptr - (unsigned long)aboot + aboot_base; break; } } } if (!target) { printf("[-] Failed to find function to patch.\n"); return 1; } tgt = NULL; for (i = 0; i < (sizeof(targets)/sizeof(targets[0])); i++) { if (targets[i].check_sigs == target) { tgt = &targets[i]; break; } } if (!tgt) { printf("[-] Unsupported aboot image.\n"); return 1; } printf("[+] Detected target %s %s build %s\n", tgt->vendor, tgt->device, tgt->build); /* Map the original boot/recovery image */ if (fstat(ifd, &st)) { printf("[-] fstat() failed.\n"); return 1; } orig = mmap(0, (st.st_size + 0x2000 + 0xfff) & ~0xfff, PROT_READ|PROT_WRITE, MAP_PRIVATE, ifd, 0); if (orig == MAP_FAILED) { printf("[-] Failed to mmap input file.\n"); return 1; } hdr = orig; loki_hdr = orig + 0x400; if (!memcmp(loki_hdr->magic, "LOKI", 4)) { printf("[-] Input file is already a Loki image.\n"); /* Copy the entire file to the output transparently */ if (write(ofd, orig, st.st_size) != st.st_size) { printf("[-] Failed to copy Loki image.\n"); return 1; } printf("[+] Copied Loki image to %s.\n", out_image); return 0; } /* Set the Loki header */ memcpy(loki_hdr->magic, "LOKI", 4); loki_hdr->recovery = recovery; strncpy(loki_hdr->build, tgt->build, sizeof(loki_hdr->build) - 1); page_size = hdr->page_size; page_mask = hdr->page_size - 1; orig_kernel_size = hdr->kernel_size; orig_ramdisk_size = hdr->ramdisk_size; printf("[+] Original kernel address: %.08x\n", hdr->kernel_addr); printf("[+] Original ramdisk address: %.08x\n", hdr->ramdisk_addr); /* Store the original values in unused fields of the header */ loki_hdr->orig_kernel_size = orig_kernel_size; loki_hdr->orig_ramdisk_size = orig_ramdisk_size; loki_hdr->ramdisk_addr = hdr->kernel_addr + ((hdr->kernel_size + page_mask) & ~page_mask); if (patch_shellcode(tgt->hdr, hdr->ramdisk_addr) < 0) { printf("[-] Failed to patch shellcode.\n"); return 1; } /* Ramdisk must be aligned to a page boundary */ hdr->kernel_size = ((hdr->kernel_size + page_mask) & ~page_mask) + hdr->ramdisk_size; /* Guarantee 16-byte alignment */ offset = tgt->check_sigs & 0xf; hdr->ramdisk_addr = tgt->check_sigs - offset; if (tgt->lg) { fake_size = page_size; hdr->ramdisk_size = page_size; } else { fake_size = 0x200; hdr->ramdisk_size = 0; } /* Write the image header */ if (write(ofd, orig, page_size) != page_size) { printf("[-] Failed to write header to output file.\n"); return 1; } page_kernel_size = (orig_kernel_size + page_mask) & ~page_mask; /* Write the kernel */ if (write(ofd, orig + page_size, page_kernel_size) != page_kernel_size) { printf("[-] Failed to write kernel to output file.\n"); return 1; } page_ramdisk_size = (orig_ramdisk_size + page_mask) & ~page_mask; /* Write the ramdisk */ if (write(ofd, orig + page_size + page_kernel_size, page_ramdisk_size) != page_ramdisk_size) { printf("[-] Failed to write ramdisk to output file.\n"); return 1; } /* Write fake_size bytes of original code to the output */ buf = malloc(fake_size); if (!buf) { printf("[-] Out of memory.\n"); return 1; } lseek(aboot_fd, tgt->check_sigs - aboot_base - offset, SEEK_SET); read(aboot_fd, buf, fake_size); if (write(ofd, buf, fake_size) != fake_size) { printf("[-] Failed to write original aboot code to output file.\n"); return 1; } /* Save this position for later */ pos = lseek(ofd, 0, SEEK_CUR); /* Write the device tree if needed */ if (hdr->dt_size) { printf("[+] Writing device tree.\n"); if (write(ofd, orig + page_size + page_kernel_size + page_ramdisk_size, hdr->dt_size) != hdr->dt_size) { printf("[-] Failed to write device tree to output file.\n"); return 1; } } lseek(ofd, pos - (fake_size - offset), SEEK_SET); /* Write the patch */ if (write(ofd, patch, sizeof(patch)) != sizeof(patch)) { printf("[-] Failed to write patch to output file.\n"); return 1; } close(ifd); close(ofd); close(aboot_fd); printf("[+] Output file written to %s\n", out_image); return 0; }