Skip to content

Instantly share code, notes, and snippets.

@xerub
Last active January 24, 2024 09:38
Star You must be signed in to star a gist
Save xerub/0161aacd7258d31c6a27584f90fa2e8c to your computer and use it in GitHub Desktop.
/*
* SEP firmware split tool
*
* Copyright (c) 2017 xerub
*/
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <mach-o/loader.h>
#define IS64(image) (*(uint8_t *)(image) & 1)
#define MACHO(p) ((*(unsigned int *)(p) & ~1) == 0xfeedface)
static const struct sepapp_t {
uint64_t phys;
uint32_t virt;
uint32_t size;
uint32_t entry;
char name[12];
/*char hash[16];*/
} *apps;
static size_t sizeof_sepapp = sizeof(struct sepapp_t);
#define UCHAR_MAX 255
static unsigned char *
boyermoore_horspool_memmem(const unsigned char* haystack, size_t hlen,
const unsigned char* needle, size_t nlen)
{
size_t last, scan = 0;
size_t bad_char_skip[UCHAR_MAX + 1]; /* Officially called:
* bad character shift */
/* Sanity checks on the parameters */
if (nlen <= 0 || !haystack || !needle)
return NULL;
/* ---- Preprocess ---- */
/* Initialize the table to default value */
/* When a character is encountered that does not occur
* in the needle, we can safely skip ahead for the whole
* length of the needle.
*/
for (scan = 0; scan <= UCHAR_MAX; scan = scan + 1)
bad_char_skip[scan] = nlen;
/* C arrays have the first byte at [0], therefore:
* [nlen - 1] is the last byte of the array. */
last = nlen - 1;
/* Then populate it with the analysis of the needle */
for (scan = 0; scan < last; scan = scan + 1)
bad_char_skip[needle[scan]] = last - scan;
/* ---- Do the matching ---- */
/* Search the haystack, while the needle can still be within it. */
while (hlen >= nlen)
{
/* scan from the end of the needle */
for (scan = last; haystack[scan] == needle[scan]; scan = scan - 1)
if (scan == 0) /* If the first byte matches, we've found it. */
return (void *)haystack;
/* otherwise, we need to skip some bytes and start again.
Note that here we are getting the skip value based on the last byte
of needle, no matter where we didn't match. So if needle is: "abcd"
then we are skipping based on 'd' and that value will be 4, and
for "abcdd" we again skip on 'd' but the value will be only 1.
The alternative of pretending that the mismatched character was
the last character is slower in the normal case (E.g. finding
"abcd" in "...azcd..." gives 4 by using 'd' but only
4-2==2 using 'z'. */
hlen -= bad_char_skip[haystack[last]];
haystack += bad_char_skip[haystack[last]];
}
return NULL;
}
static size_t
restore_linkedit(uint8_t *p, size_t size)
{
unsigned i;
struct mach_header *hdr = (struct mach_header *)p;
uint64_t min = -1;
uint64_t delta = 0;
int is64 = 0;
uint8_t *q;
if (size < 1024) {
return -1;
}
if (!MACHO(p)) {
return -1;
}
if (IS64(p)) {
is64 = 4;
}
q = p + sizeof(struct mach_header) + is64;
for (i = 0; i < hdr->ncmds; i++) {
const struct load_command *cmd = (struct load_command *)q;
if (cmd->cmd == LC_SEGMENT) {
const struct segment_command *seg = (struct segment_command *)q;
if (strcmp(seg->segname, "__PAGEZERO") && min > seg->vmaddr) {
min = seg->vmaddr;
}
}
if (cmd->cmd == LC_SEGMENT_64) {
const struct segment_command_64 *seg = (struct segment_command_64 *)q;
if (strcmp(seg->segname, "__PAGEZERO") && min > seg->vmaddr) {
min = seg->vmaddr;
}
}
q = q + cmd->cmdsize;
}
q = p + sizeof(struct mach_header) + is64;
for (i = 0; i < hdr->ncmds; i++) {
const struct load_command *cmd = (struct load_command *)q;
if (cmd->cmd == LC_SEGMENT) {
struct segment_command *seg = (struct segment_command *)q;
if (!strcmp(seg->segname, "__LINKEDIT")) {
delta = seg->vmaddr - min - seg->fileoff;
seg->fileoff += delta;
}
}
if (cmd->cmd == LC_SEGMENT_64) {
struct segment_command_64 *seg = (struct segment_command_64 *)q;
if (!strcmp(seg->segname, "__LINKEDIT")) {
delta = seg->vmaddr - min - seg->fileoff;
seg->fileoff += delta;
}
}
if (cmd->cmd == LC_SYMTAB) {
struct symtab_command *sym = (struct symtab_command *)q;
if (sym->stroff) sym->stroff += delta;
if (sym->symoff) sym->symoff += delta;
}
q = q + cmd->cmdsize;
}
return 0;
}
static size_t
calc_size(const uint8_t *p, size_t size)
{
unsigned i;
const struct mach_header *hdr = (struct mach_header *)p;
const uint8_t *q = p + sizeof(struct mach_header);
size_t end, tsize = 0;
if (size < 1024) {
return 0;
}
if (!MACHO(p)) {
return 0;
}
if (IS64(p)) {
q += 4;
}
for (i = 0; i < hdr->ncmds; i++) {
const struct load_command *cmd = (struct load_command *)q;
if (cmd->cmd == LC_SEGMENT) {
const struct segment_command *seg = (struct segment_command *)q;
end = seg->fileoff + seg->filesize;
if (tsize < end) {
tsize = end;
}
}
if (cmd->cmd == LC_SEGMENT_64) {
const struct segment_command_64 *seg = (struct segment_command_64 *)q;
end = seg->fileoff + seg->filesize;
if (tsize < end) {
tsize = end;
}
}
q = q + cmd->cmdsize;
}
return tsize;
}
uint8_t *kernel = MAP_FAILED;
size_t kernel_size = 0;
static int kernel_fd = -1;
static int
init_kernel(const char *filename)
{
kernel_fd = open(filename, O_RDONLY);
if (kernel_fd < 0) {
return -1;
}
kernel_size = lseek(kernel_fd, 0, SEEK_END);
kernel = mmap(NULL, kernel_size, PROT_READ, MAP_PRIVATE, kernel_fd, 0);
if (kernel == MAP_FAILED) {
close(kernel_fd);
kernel_fd = -1;
return -1;
}
return 0;
}
static void
term_kernel(void)
{
munmap(kernel, kernel_size);
close(kernel_fd);
}
static int
write_file(const char *name, const void *buf, size_t size)
{
int fd;
size_t sz;
fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
return -1;
}
sz = write(fd, buf, size);
close(fd);
return (sz == size) ? 0 : -1;
}
static int
restore_file(unsigned index, const unsigned char *buf, size_t size, int restore)
{
int rv;
void *tmp;
char name[256];
char tail[12 + 1];
if (index == 1 && size > 4096) {
unsigned char *toc = boyermoore_horspool_memmem(buf + size - 4096, 4096, (unsigned char *)"SEPOS ", 12);
if (toc) {
unsigned char *p = boyermoore_horspool_memmem(toc + 1, 64, (unsigned char *)"SEP", 3);
if (p) {
sizeof_sepapp = p - toc;
}
apps = (struct sepapp_t *)(toc - offsetof(struct sepapp_t, name));
}
}
if (apps && buf > (unsigned char *)apps) {
char *p;
memcpy(tail, apps->name, 12);
for (p = tail + 12; p > tail && p[-1] == ' '; p--) {
continue;
}
*p = '\0';
printf("%-12s phys 0x%llx, virt 0x%x, size 0x%x, entry 0x%x\n", tail, apps->phys, apps->virt, apps->size, apps->entry);
apps = (struct sepapp_t *)((char *)apps + sizeof_sepapp);
} else {
if (index == 0) {
strcpy(tail, "boot");
printf("%s\n", tail);
} else if (index == 1) {
strcpy(tail, "kernel");
printf("%s\n", tail);
} else {
*tail = '\0';
printf("macho%d\n", index);
}
}
snprintf(name, sizeof(name), "sepdump%02u_%s", index, tail);
if (!restore) {
return write_file(name, buf, size);
}
tmp = malloc(size);
if (!tmp) {
return -1;
}
memcpy(tmp, buf, size);
restore_linkedit(tmp, size);
rv = write_file(name, tmp, size);
free(tmp);
return rv;
}
static int
split(int restore)
{
size_t i;
unsigned j = 0;
size_t last = 0;
for (i = 0; i < kernel_size; i += 4) {
size_t sz = calc_size(kernel + i, kernel_size - i);
if (sz) {
restore_file(j++, kernel + last, i - last, restore);
last = i;
i += sz - 4;
}
}
restore_file(j, kernel + last, i - last, restore);
return 0;
}
int
main(int argc, char **argv)
{
int rv;
const char *krnl = (argc > 1) ? argv[1] : "sep";
rv = init_kernel(krnl);
if (rv) {
fprintf(stderr, "[e] cannot read kernel\n");
return -1;
}
rv = split(1);
term_kernel();
return 0;
}
@rumblefrog
Copy link

Thanks!

@jihyo-song
Copy link

Noob question, how to use img4lib & sepsplit.c to decrypt SEP-firmware ? Actually i want to try to decrypt n53ap sep. Thank you!

@Daemonceltics
Copy link

Daemonceltics commented Aug 19, 2017

$ ./sepsplit sep-firmware.n51.RELEASE.im4p.decrypt
boot

Anyone get this problem?(iOS 10.2 firmware)

@matteyeux
Copy link

@Daemonceltics it is not decryted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment