Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9046cdaa authored by Seth Shelnutt's avatar Seth Shelnutt Committed by Ricardo Cerqueira
Browse files

Recovery: Loki patching support

Add support for automatically checking and making loki images
for locked bootloader bypass on loki supported devices. Loki
support is enabled with ENABLE_LOKI_RECOVERY.

Change-Id: I81fe267d58f0dda18a790e25df223f011dca3e37
parent 83e26bfb
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -58,6 +58,12 @@ ifeq ($(BOARD_USE_CUSTOM_RECOVERY_FONT),)
  BOARD_USE_CUSTOM_RECOVERY_FONT := \"font_10x18.h\"
endif

ifeq ($(ENABLE_LOKI_RECOVERY),true)
  LOCAL_CFLAGS += -DENABLE_LOKI
  LOCAL_SRC_FILES += \
    compact_loki.c
endif

BOARD_RECOVERY_CHAR_WIDTH := $(shell echo $(BOARD_USE_CUSTOM_RECOVERY_FONT) | cut -d _  -f 2 | cut -d . -f 1 | cut -d x -f 1)
BOARD_RECOVERY_CHAR_HEIGHT := $(shell echo $(BOARD_USE_CUSTOM_RECOVERY_FONT) | cut -d _  -f 2 | cut -d . -f 1 | cut -d x -f 2)

+12 −2
Original line number Diff line number Diff line
@@ -99,7 +99,9 @@ void *adb_sideload_thread(void* v) {

int
apply_from_adb() {

#ifdef ENABLE_LOKI
    int loki_support;
#endif
    stop_adbd();
    set_usb_driver(1);

@@ -153,6 +155,14 @@ apply_from_adb() {
    }

    remove(ADB_SIDELOAD_FILENAME);

#ifdef ENABLE_LOKI
    if(loki_support_enabled) {
       ui_print("Checking if loki-fying is needed");
       int result;
       if(result = loki_check()) {
           return result;
       }
    }
#endif
    return install_status;
}
+5 −0
Original line number Diff line number Diff line
@@ -47,6 +47,11 @@ int ui_was_niced();
int ui_get_text_cols();
void ui_increment_frame();

#ifdef ENABLE_LOKI
// Toggle for loki support
extern int loki_support_enabled;
#endif

// Display some header text followed by a menu of items, which appears
// at the top of the screen (in place of any scrolling ui_print()
// output, if necessary).

compact_loki.c

0 → 100644
+643 −0
Original line number Diff line number Diff line
/*
 * 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)
 * modified for use in recovery by Seth Shelnutt
 *
 */
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "compact_loki.h"
#include "common.h"

#define VERSION "1.9"

#define BOOT_MAGIC_SIZE 8
#define BOOT_NAME_SIZE 16
#define BOOT_ARGS_SIZE 512

struct boot_img_hdr
{
	unsigned char magic[BOOT_MAGIC_SIZE];
	unsigned kernel_size;	/* size in bytes */
	unsigned kernel_addr;	/* physical load addr */
	unsigned ramdisk_size;	/* size in bytes */
	unsigned ramdisk_addr;	/* physical load addr */
	unsigned second_size;	/* size in bytes */
	unsigned second_addr;	/* physical load addr */
	unsigned tags_addr;		/* physical addr for kernel tags */
	unsigned page_size;		/* flash page size we assume */
	unsigned dt_size;		/* device_tree in bytes */
	unsigned unused;		/* future expansion: should be 0 */
	unsigned char name[BOOT_NAME_SIZE];	/* asciiz product name */
	unsigned char cmdline[BOOT_ARGS_SIZE];
	unsigned id[8];			/* timestamp / checksum / sha1 / etc */
};

struct loki_hdr
{
	unsigned char magic[4];		/* 0x494b4f4c */
	unsigned int recovery;		/* 0 = boot.img, 1 = recovery.img */
	unsigned char build[128];	/* Build number */
};

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 = "DoCoMo",
		.device = "LG Optimus G",
		.build = "L01E20b",
		.check_sigs = 0x88F10E48,
		.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 = "Virgin Mobile",
		.device = "LG Optimus F3",
		.build = "LS720ZV5",
		.check_sigs = 0x88f108f0,
		.hdr = 0x88f854f4,
		.lg = 1,
	},
};

#define PATTERN1 "\xf0\xb5\x8f\xb0\x06\x46\xf0\xf7"
#define PATTERN2 "\xf0\xb5\x8f\xb0\x07\x46\xf0\xf7"
#define PATTERN3 "\x2d\xe9\xf0\x41\x86\xb0\xf1\xf7"
#define PATTERN4 "\x2d\xe9\xf0\x4f\xad\xf5\xc6\x6d"

#define ABOOT_BASE_SAMSUNG 0x88dfffd8
#define ABOOT_BASE_LG 0x88efffd8

unsigned char patch[] =
"\xfe\xb5"
"\x0b\x4d"
"\xa8\x6a"
"\xab\x68"
"\x98\x42"
"\x0e\xd0"
"\xee\x69"
"\x09\x4c"
"\xef\x6a"
"\x07\xf5\x80\x57"
"\x0f\xce"
"\x0f\xc4"
"\x10\x3f"
"\xfb\xdc"
"\xa8\x6a"
"\x04\x49"
"\xea\x6a"
"\xa8\x60"
"\x69\x61"
"\x2a\x61"
"\x00\x20"
"\xfe\xbd"
"\x00\x00"
"\xff\xff\xff\xff"		/* Replace with header address */
"\x00\x00\x20\x82";

int loki_patch_shellcode(unsigned int addr)
{

	unsigned int i;
	unsigned int *ptr;

	for (i = 0; i < sizeof(patch); i++) {
		ptr = (unsigned int *)&patch[i];
		if (*ptr == 0xffffffff) {
			*ptr = addr;
			return 0;
		}
		else if (*ptr == addr) {
			return 0;
		}
	}

	return -1;
}

int loki_patch(char *partition, char *partitionPath)
{

	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, "boot")) {
		recovery = 0;
	} else if (!strcmp(partition, "recovery")) {
		recovery = 1;
	} else {
		ui_print("[+] First argument must be \"boot\" or \"recovery\".\n");
		return 1;
	}

	/* Open input files */
	aboot_fd = open("/dev/block/platform/msm_sdcc.1/by-name/aboot", O_RDONLY);
	if (aboot_fd < 0) {
		ui_print("[-] Failed to open %s for reading.\n", "/dev/block/platform/msm_sdcc.1/by-name/aboot");
		return 1;
	}

	ifd = open(partitionPath, O_RDONLY);
	if (ifd < 0) {
		ui_print("[-] Failed to open %s for reading.\n", partitionPath);
		return 1;
	}

	ofd = open(Loki_Image, O_WRONLY|O_CREAT|O_TRUNC, 0644);
	if (ofd < 0) {
		ui_print("[-] Failed to open %s for writing.\n", Loki_Image);
		return 1;
	}

	/* Find the signature checking function via pattern matching */
	if (fstat(aboot_fd, &st)) {
		ui_print("[-] fstat() failed.\n");
		return 1;
	}

	/* Verify the to-be-patched address matches the known code pattern */
	aboot = mmap(0, (524288 + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, aboot_fd, 0);
	if (aboot == MAP_FAILED) {
		printf("[-] Failed to mmap aboot.\n");
		return 1;
	}

	target = 0;

	for (ptr = aboot; ptr < aboot + 524288 - 0x1000; ptr++) {
		if (!memcmp(ptr, PATTERN1, 8) ||
			!memcmp(ptr, PATTERN2, 8) ||
			!memcmp(ptr, PATTERN3, 8)) {

			aboot_base = ABOOT_BASE_SAMSUNG;
			target = (unsigned long)ptr - (unsigned long)aboot + aboot_base;
			break;
		}

		if (!memcmp(ptr, PATTERN4, 8)) {

			aboot_base = ABOOT_BASE_LG;
			target = (unsigned long)ptr - (unsigned long)aboot + aboot_base;
			break;
		}
	}

	if (!target) {
		ui_print("[-] 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) {
		ui_print("[-] Unsupported aboot image.\n");
		return 1;
	}

	if (loki_patch_shellcode(tgt->hdr) < 0) {
		ui_print("[-] Failed to patch shellcode.\n");
		return 1;
	}

	/* Map the original boot/recovery image */
	if (fstat(ifd, &st)) {
		ui_print("[-] fstat() failed.\n");
		return 1;
	}

	orig = mmap(0, (25165824 + 0x2000 + 0xfff) & ~0xfff, PROT_READ|PROT_WRITE, MAP_PRIVATE, ifd, 0);
	if (orig == MAP_FAILED) {
		ui_print("[-] Failed to mmap input file.\n");
		return 1;
	}

	hdr = orig;
	loki_hdr = orig + 0x400;

	if (!memcmp(loki_hdr->magic, "LOKI", 4)) {
		ui_print("[-] Input file is already a Loki image.\n");

		/* Copy the entire file to the output transparently */
		if (write(ofd, orig, 25165824) != 25165824) {
			ui_print("[-] Failed to copy Loki image.\n");
			return 1;
		}

		ui_print("[+] Copied Loki image to %s.\n", Loki_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;

	/* Store the original values in uses fields of the header */
	hdr->dt_size = orig_kernel_size;
	hdr->unused = orig_ramdisk_size;
	hdr->second_addr = hdr->kernel_addr + ((hdr->kernel_size + page_mask) & ~page_mask);

	/* 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) {
		ui_print("[-] 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) {
		ui_print("[-] 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) {
		ui_print("[-] 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) {
		ui_print("[-] 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) {
		ui_print("[-] Failed to write original aboot code to output file.\n");
		return 1;
	}

	pos = lseek(ofd, 0, SEEK_CUR);
	lseek(ofd, pos - (fake_size - offset), SEEK_SET);

	/* Write the patch */
	if (write(ofd, patch, sizeof(patch)) != sizeof(patch)) {
		ui_print("[-] Failed to write patch to output file.\n");
		return 1;
	}

	close(ifd);
	close(ofd);
	close(aboot_fd);

	return 0;
}

int loki_check(){
    if(loki_check_partition(BOOT_PARTITION)){
        if(loki_patch("boot","/dev/block/platform/msm_sdcc.1/by-name/boot")){
             ui_set_background(BACKGROUND_ICON_ERROR);
             ui_print("Error loki-ifying the boot image.\n");
             return 1;
        }
        if(loki_flash("/dev/block/platform/msm_sdcc.1/by-name/boot")){
             ui_set_background(BACKGROUND_ICON_ERROR);
             ui_print("Error loki-flashing the boot image.\n");
             return 1;
        }
    }
    if(loki_check_partition(RECOVERY_PARTITION)){
        if(loki_patch("recovery","/dev/block/platform/msm_sdcc.1/by-name/recovery")){
             ui_set_background(BACKGROUND_ICON_ERROR);
             ui_print("Error loki-ifying the recovery image.\n");
             return 1;
        }
        if(loki_flash("/dev/block/platform/msm_sdcc.1/by-name/recovery")){
             ui_set_background(BACKGROUND_ICON_ERROR);
             ui_print("Error loki-flashing the recovery image.\n");
             return 1;
        }
    }

    return 0;
}

int loki_check_partition(char *partition)
{
	int ifd;
	void *orig;
	struct stat st;
	struct boot_img_hdr *hdr;
	struct loki_hdr *loki_hdr;
	char *buf;

	ifd = open(partition, O_RDONLY);
	if (ifd < 0) {
		ui_print("[-] Failed to open %s for reading.\n", partition);
		return 1;
	}

	/* Map the image to be flashed */
	if (fstat(ifd, &st)) {
		ui_print("[-] fstat() failed.\n");
		return 1;
	}

	orig = mmap(0, (25165824 + 0x2000 + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, ifd, 0);
	if (orig == MAP_FAILED) {
		ui_print("[-] Failed to mmap Loki image.\n");
		return 1;
	}

	hdr = orig;
	loki_hdr = orig + 0x400;

	/* Verify this is a Loki image */
	if (memcmp(loki_hdr->magic, "LOKI", 4)) {
		ui_print("%s needs lokifying.\n", partition);
		return 1;
	}
        else {
               return 0;
        }
}

int loki_flash(char *partition)
{

	int ifd, aboot_fd, ofd, recovery, offs, match;
	void *orig, *aboot, *patch;
	struct stat st;
	struct boot_img_hdr *hdr;
	struct loki_hdr *loki_hdr;
	char prop[256], outfile[1024], buf[4096];


	if (!strcmp(partition, "/dev/block/platform/msm_sdcc.1/by-name/boot")) {
		recovery = 0;
	} else if (!strcmp(partition, "/dev/block/platform/msm_sdcc.1/by-name/recovery")) {
		recovery = 1;
	} else {
		ui_print("[+] First argument must be \"boot\" or \"recovery\".\n");
		return 1;
	}

	/* Verify input file */
	aboot_fd = open("/dev/block/platform/msm_sdcc.1/by-name/aboot", O_RDONLY);
	if (aboot_fd < 0) {
		ui_print("[-] Failed to open aboot for reading.\n");
		return 1;
	}

	ifd = open(Loki_Image, O_RDONLY);
	if (ifd < 0) {
		ui_print("[-] Failed to open %s for reading.\n", Loki_Image);
		return 1;
	}

	/* Map the image to be flashed */
	if (fstat(ifd, &st)) {
		ui_print("[-] fstat() failed.\n");
		return 1;
	}

	orig = mmap(0, (25165824 + 0x2000 + 0xfff) & ~0xfff, PROT_READ, MAP_PRIVATE, ifd, 0);
	if (orig == MAP_FAILED) {
		ui_print("[-] Failed to mmap Loki image.\n");
		return 1;
	}

	hdr = orig;
	loki_hdr = orig + 0x400;

	/* Verify this is a Loki image */
	if (memcmp(loki_hdr->magic, "LOKI", 4)) {
		ui_print("[-] Input file is not a Loki image.\n");
		return 1;
	}

	/* Verify this is the right type of image */
	if (loki_hdr->recovery != recovery) {
		ui_print("[-] Loki image is not a %s image.\n", recovery ? "recovery" : "boot");
		return 1;
	}

	/* Verify the to-be-patched address matches the known code pattern */
	aboot = mmap(0, 0x40000, PROT_READ, MAP_PRIVATE, aboot_fd, 0);
	if (aboot == MAP_FAILED) {
		ui_print("[-] Failed to mmap aboot.\n");
		return 1;
	}

	match = 0;

	for (offs = 0; offs < 0x10; offs += 0x4) {

		if (hdr->ramdisk_addr < ABOOT_BASE_LG)
			patch = hdr->ramdisk_addr - ABOOT_BASE_SAMSUNG + aboot + offs;
		else
			patch = hdr->ramdisk_addr - ABOOT_BASE_LG + aboot + offs;

		if (patch < aboot || patch > aboot + 0x40000 - 8) {
			ui_print("[-] Invalid .lok file.\n");
			return 1;
		}

		if (!memcmp(patch, PATTERN1, 8) ||
			!memcmp(patch, PATTERN2, 8) ||
			!memcmp(patch, PATTERN3, 8) ||
			!memcmp(patch, PATTERN4, 8)) {

			match = 1;
			break;
		}
	}

	if (!match) {
		ui_print("[-] Loki aboot version does not match device.\n");
		return 1;
	}

	ui_print("[+] Loki validation passed, flashing image.\n");

	snprintf(outfile, sizeof(outfile),
			 "/dev/block/platform/msm_sdcc.1/by-name/%s",
			 recovery ? "recovery" : "boot");

	ofd = open(outfile, O_WRONLY);
	if (ofd < 0) {
		ui_print("[-] Failed to open output block device.\n");
		return 1;
	}

	if (write(ofd, orig, st.st_size) != st.st_size) {
		ui_print("[-] Failed to write to block device.\n");
		return 1;
	}

	ui_print("[+] Loki flashing complete!\n");

	close(ifd);
	close(aboot_fd);
	close(ofd);

	return 0;
}

compact_loki.h

0 → 100644
+29 −0
Original line number Diff line number Diff line
/*
 * By Seth Shelnutt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define Loki_Image "/tmp/loki_image"
#define BOOT_PARTITION "/dev/block/platform/msm_sdcc.1/by-name/boot"
#define RECOVERY_PARTITION "/dev/block/platform/msm_sdcc.1/by-name/recovery"

int loki_patch_shellcode(unsigned int addr);

int loki_patch(char *partition, char *partitionPath);

int loki_flash(char *partition);

int loki_check_partition(char *partition);

int loki_check();
Loading