You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
694 lines
14 KiB
694 lines
14 KiB
/*
|
|
* 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;
|
|
}
|
|
|