diff options
Diffstat (limited to 'src/mbn.c')
-rw-r--r-- | src/mbn.c | 490 |
1 files changed, 440 insertions, 50 deletions
@@ -1,9 +1,10 @@ /* * mbn.c - * support for .mbn file format (found in .bbfw files) + * support for Qualcomm MBN (Modem Binary) formats * * Copyright (c) 2012 Martin Szulecki. All Rights Reserved. * Copyright (c) 2012 Nikias Bassen. All Rights Reserved. + * Copyright (c) 2025 Visual Ehrmanntraut <visual@chefkiss.dev>. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,67 +26,456 @@ #include "mbn.h" #include "common.h" -mbn_file* mbn_parse(const void* data, size_t size) +#define MBN_V1_MAGIC "\x0A\x00\x00\x00" +#define MBN_V1_MAGIC_SIZE 4 + +#pragma pack(push, 1) +typedef struct { + uint32_t type; // the signed .mbn files have 0xA as value. + uint32_t unk_0x04; + uint32_t unk_0x08; + uint32_t unk_0x0c; + uint32_t data_size; // data_size = total_size - sizeof(mbn_header) + uint32_t sig_offset; // real offset = enc_sig_offset & 0xFFFFFF00 + uint32_t unk_0x18; + uint32_t unk_0x1c; + uint32_t unk_0x20; + uint32_t unk_0x24; +} mbn_header_v1; + +#define MBN_V2_MAGIC "\xD1\xDC\x4B\x84\x34\x10\xD7\x73" +#define MBN_V2_MAGIC_SIZE 8 + +typedef struct { + unsigned char magic1[8]; + uint32_t unk_0x08; + uint32_t unk_0x0c; // 0xFFFFFFFF + uint32_t unk_0x10; // 0xFFFFFFFF + uint32_t header_size; + uint32_t unk_0x18; + uint32_t data_size; // data_size = total_size - sizeof(mbn_header_v2) + uint32_t sig_offset; + uint32_t unk_0x24; + uint32_t unk_0x28; + uint32_t unk_0x2c; + uint32_t unk_0x30; + uint32_t unk_0x34; // 0x1 + uint32_t unk_0x38; // 0x1 + uint32_t unk_0x3c; // 0xFFFFFFFF + uint32_t unk_0x40; // 0xFFFFFFFF + uint32_t unk_0x44; // 0xFFFFFFFF + uint32_t unk_0x48; // 0xFFFFFFFF + uint32_t unk_0x4c; // 0xFFFFFFFF +} mbn_header_v2; + +#define MBN_BIN_MAGIC "\x04\x00\xEA\x6C\x69\x48\x55" +#define MBN_BIN_MAGIC_SIZE 7 +#define MBN_BIN_MAGIC_OFFSET 1 // we ignore the first byte + +typedef struct { + unsigned char magic[8]; + uint32_t unk_0x08; + uint32_t version; + uint32_t total_size; // size including header + uint32_t unk_0x14; // some offset +} mbn_bin_header; + +typedef struct +{ + uint32_t reserved; + uint32_t version; + uint32_t common_metadata_size; + uint32_t qti_metadata_size; + uint32_t oem_metadata_size; + uint32_t hash_table_size; + uint32_t qti_signature_size; + uint32_t qti_certificate_chain_size; + uint32_t oem_signature_size; + uint32_t oem_certificate_chain_size; +} mbn_v7_header; + +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_OSABI 7 +#define EI_ABIVERSION 8 +#define EI_PAD 9 +#define EI_NIDENT 16 + +#define ELFMAG0 0x7F +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' +#define ELFCLASSNONE 0 +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +typedef struct +{ + uint8_t e_ident[EI_NIDENT]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint32_t e_entry; + uint32_t e_phoff; + uint32_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +} elf32_header; + +typedef struct +{ + uint8_t e_ident[EI_NIDENT]; + uint16_t e_type; + uint16_t e_machine; + uint32_t e_version; + uint64_t e_entry; + uint64_t e_phoff; + uint64_t e_shoff; + uint32_t e_flags; + uint16_t e_ehsize; + uint16_t e_phentsize; + uint16_t e_phnum; + uint16_t e_shentsize; + uint16_t e_shnum; + uint16_t e_shstrndx; +} elf64_header; + +typedef struct +{ + uint32_t p_type; + uint32_t p_offset; + uint32_t p_vaddr; + uint32_t p_paddr; + uint32_t p_filesz; + uint32_t p_memsz; + uint32_t p_flags; + uint32_t p_align; +} elf32_pheader; + +typedef struct +{ + uint32_t p_type; + uint32_t p_flags; + uint64_t p_offset; + uint64_t p_vaddr; + uint64_t p_paddr; + uint64_t p_filesz; + uint64_t p_memsz; + uint64_t p_align; +} elf64_pheader; +#pragma pack(pop) + +static int mbn_is_valid_elf(const uint8_t* e_ident, size_t size) +{ + return size >= EI_NIDENT && e_ident[EI_MAG0] == ELFMAG0 && + e_ident[EI_MAG1] == ELFMAG1 && e_ident[EI_MAG2] == ELFMAG2 && + e_ident[EI_MAG3] == ELFMAG3 && e_ident[EI_CLASS] != ELFCLASSNONE; +} + +static int mbn_is_64bit_elf(const uint8_t* e_ident) { - mbn_file* mbn = (mbn_file*)malloc(sizeof(mbn_file)); - if (!mbn) { - return NULL; - } - memset(mbn, '\0', sizeof(mbn_file)); - mbn->data = malloc(size); - mbn->size = size; - memcpy(mbn->data, data, size); - /* FIXME: header parsing is not big endian safe */ - if (memcmp(data, MBN_V2_MAGIC, MBN_V2_MAGIC_SIZE) == 0) { - mbn->version = 2; - memcpy(&mbn->header.v2, data, sizeof(mbn_header_v2)); - mbn->parsed_size = mbn->header.v2.data_size + sizeof(mbn_header_v2); - } else if (memcmp(data, MBN_V1_MAGIC, MBN_V1_MAGIC_SIZE) == 0) { - mbn->version = 1; - memcpy(&mbn->header.v1, data, sizeof(mbn_header_v1)); - mbn->parsed_size = mbn->header.v1.data_size + sizeof(mbn_header_v1); - } else if (memcmp(data, BIN_MAGIC, BIN_MAGIC_SIZE) == 0) { - mbn->version = 3; - memcpy(&mbn->header.bin, data, sizeof(bin_header)); - mbn->parsed_size = mbn->header.bin.total_size; - } else if (memcmp(data, ELF_MAGIC, ELF_MAGIC_SIZE) == 0) { - mbn->version = 4; - memcpy(&mbn->header.elf, data, sizeof(elf_header)); - // we cheat here since we don't parse the actual ELF file - mbn->parsed_size = mbn->size; + return e_ident[EI_CLASS] == ELFCLASS64; +} + +void* mbn_stitch(const void* data, size_t data_size, const void* blob, size_t blob_size) +{ + if (!data) { + logger(LL_ERROR, "%s: data is NULL\n", __func__); + return NULL; + } + + if (!data_size) { + logger(LL_ERROR, "%s: data size is 0\n", __func__); + return NULL; + } + + if (!blob) { + logger(LL_ERROR, "%s: blob is NULL\n", __func__); + return NULL; + } + + if (!blob_size) { + logger(LL_ERROR, "%s: blob size is 0\n", __func__); + return NULL; + } + + size_t parsed_size = 0; + if (data_size > MBN_V2_MAGIC_SIZE && memcmp(data, MBN_V2_MAGIC, MBN_V2_MAGIC_SIZE) == 0) { + parsed_size = ((mbn_header_v2*)data)->data_size + sizeof(mbn_header_v2); + logger(LL_DEBUG, "%s: encountered MBN v2 image, parsed_size = 0x%zx\n", __func__, parsed_size); + } else if (data_size > MBN_V1_MAGIC_SIZE && memcmp(data, MBN_V1_MAGIC, MBN_V1_MAGIC_SIZE) == 0) { + parsed_size = ((mbn_header_v1*)data)->data_size + sizeof(mbn_header_v1); + logger(LL_DEBUG, "%s: encountered MBN v1 image, parsed_size = 0x%zx\n", __func__, parsed_size); + } else if (data_size > MBN_BIN_MAGIC_SIZE+MBN_BIN_MAGIC_OFFSET && memcmp((uint8_t*)data+MBN_BIN_MAGIC_OFFSET, (uint8_t*)MBN_BIN_MAGIC, MBN_BIN_MAGIC_SIZE) == 0) { + parsed_size = ((mbn_bin_header*)data)->total_size; + logger(LL_DEBUG, "%s: encountered MBN BIN image, parsed_size = 0x%zx\n", __func__, parsed_size); + } else if (mbn_is_valid_elf(data, data_size)) { + if (mbn_is_64bit_elf(data)) { + const elf64_header* ehdr = data; + const elf64_pheader* phdr = data + ehdr->e_phoff; + if (ehdr->e_phnum == 0) { + logger(LL_ERROR, "%s: ELF has no program sections\n", __func__); + return NULL; + } + uint64_t last_off = 0; + uint16_t last_index = 0; + for (uint16_t i = 0; i < ehdr->e_phnum; i++) { + if (phdr[i].p_offset > last_off) { + last_off = phdr[i].p_offset; + last_index = i; + } + } + parsed_size = last_off + phdr[last_index].p_filesz; + } else { + const elf32_header* ehdr = data; + const elf32_pheader* phdr = data + ehdr->e_phoff; + if (ehdr->e_phnum == 0) { + logger(LL_ERROR, "%s: ELF has no program sections\n", __func__); + return NULL; + } + uint32_t last_off = 0; + uint16_t last_index = 0; + for (uint16_t i = 0; i < ehdr->e_phnum; i++) { + if (phdr[i].p_offset > last_off) { + last_off = phdr[i].p_offset; + last_index = i; + } + } + parsed_size = last_off + phdr[last_index].p_filesz; + } + logger(LL_DEBUG, "%s: encountered ELF image, parsed_size = 0x%zx\n", __func__, parsed_size); } else { - logger(LL_DEBUG, "Unknown file format passed to %s\n", __func__); + logger(LL_WARNING, "Unknown file format passed to %s\n", __func__); } - if (mbn->parsed_size != mbn->size) { - logger(LL_WARNING, "Size mismatch when parsing MBN file. Continuing anyway.\n"); + if (parsed_size != data_size) { + logger(LL_WARNING, "%s: size mismatch for MBN data, expected 0x%zx, input size 0x%zx\n", __func__, parsed_size, data_size); } - return mbn; + + off_t stitch_offset = data_size - blob_size; + if (stitch_offset + blob_size > data_size) { + logger(LL_ERROR, "%s: stitch offset (0x%llx) + size (0x%zx) is larger than the destination (0x%zx)\n", __func__, stitch_offset, blob_size, data_size); + return NULL; + } + + void* buf = malloc(data_size); + if (buf == NULL) { + logger(LL_ERROR, "out of memory\n"); + return NULL; + } + + memcpy(buf, data, data_size); + logger(LL_DEBUG, "%s: stitching mbn at 0x%llx, size 0x%zx\n", __func__, stitch_offset, blob_size); + memcpy(buf + stitch_offset, blob, blob_size); + + return buf; } -void mbn_free(mbn_file* mbn) +// the sum of header size and all sizes inside it must fit within the size of +// the data +static int mbn_v7_header_sizes_valid(const mbn_v7_header* header, size_t size) { - if (mbn) { - if (mbn->data) { - free(mbn->data); - } - free(mbn); - } + return (sizeof(*header) + header->common_metadata_size + + header->qti_metadata_size + header->oem_metadata_size + + header->hash_table_size + header->qti_signature_size + + header->qti_certificate_chain_size + header->oem_signature_size + + header->oem_certificate_chain_size) <= size; +} + +// 0xE0 == sizeof(mav25_authority_meta_field_t), 0x68 == +// kExpectedOEMSignatureSize, 0xD20 == kExpectedOEMCertChainSize +static int mbn_v7_header_sizes_expected(const mbn_v7_header* header) +{ + return (header->qti_metadata_size == 0 || header->qti_metadata_size == 0xE0) && + (header->oem_metadata_size == 0 || header->oem_metadata_size == 0xE0) && + (header->oem_signature_size == 0 || header->oem_signature_size == 0x68) && + (header->oem_certificate_chain_size == 0 || header->oem_certificate_chain_size == 0xD20); +} + +static void mbn_v7_log_header(const mbn_v7_header* header, const char* func, const char* prefix) +{ + logger(LL_DEBUG, + "%s: %s header {version=0x%x, common_metadata_size=0x%x, " + "qti_metadata_size=0x%x, oem_metadata_size=0x%x, hash_table_size=0x%x, " + "qti_signature_size=0x%x, qti_certificate_chain_size=0x%x, " + "oem_signature_size=0x%x, oem_certificate_chain_size=0x%x}", + func, + prefix, + header->version, + header->common_metadata_size, + header->qti_metadata_size, + header->oem_metadata_size, + header->hash_table_size, + header->qti_signature_size, + header->qti_certificate_chain_size, + header->oem_signature_size, + header->oem_certificate_chain_size + ); } -int mbn_update_sig_blob(mbn_file* mbn, const void* sigdata, size_t siglen) +void* mbn_mav25_stitch(const void* data, size_t data_size, const void* blob, size_t blob_size) { - if (!mbn) { - logger(LL_ERROR, "%s: no data\n", __func__); - return -1; + if (!data) { + logger(LL_ERROR, "%s: data is NULL\n", __func__); + return NULL; } - mbn->parsed_sig_offset = mbn->size - siglen; - if ((mbn->parsed_sig_offset + siglen) > mbn->size) { - logger(LL_ERROR, "%s: signature is larger than mbn file size\n", __func__); - return -1; + + if (!data_size) { + logger(LL_ERROR, "%s: data size is 0\n", __func__); + return NULL; } - memcpy(mbn->data + mbn->parsed_sig_offset, sigdata, siglen); + if (!blob) { + logger(LL_ERROR, "%s: blob is NULL\n", __func__); + return NULL; + } - return 0; -} + if (!blob_size) { + logger(LL_ERROR, "%s: blob size is 0\n", __func__); + return NULL; + } + + if (!mbn_is_valid_elf(data, data_size)) { + logger(LL_ERROR, "%s: data is not a valid ELF\n", __func__); + return NULL; + } + + if (sizeof(mbn_v7_header) > blob_size) { + logger(LL_ERROR, "%s: header is bigger than blob\n", __func__); + return NULL; + } + + const mbn_v7_header* src_header = blob; + mbn_v7_log_header(src_header, __func__, "src"); + + if (src_header->version != 7) { + logger(LL_ERROR, "%s: src header version (0x%x) is incorrect\n", __func__, src_header->version); + return NULL; + } + + // NOTE: Apple does weird stuff, in this case blob is smaller than + // the sizes the header reports, so we can't check their validity. + if (!mbn_v7_header_sizes_expected(src_header)) { + logger(LL_WARNING, "%s: header sizes in header are unexpected (qti_metadata_size=0x%x, oem_metadata_size=0x%x, oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)\n", __func__, src_header->qti_metadata_size, src_header->oem_metadata_size, src_header->oem_signature_size, src_header->oem_certificate_chain_size); + } + + off_t sect_off; + size_t sect_size; + if (mbn_is_64bit_elf(data)) { + const elf64_header* ehdr = data; + const elf64_pheader* phdr = data + ehdr->e_phoff; + if (ehdr->e_phnum == 0) { + logger(LL_ERROR, "%s: ELF has no program sections\n", __func__); + return NULL; + } + if ((ehdr->e_phoff + ehdr->e_phnum * sizeof(elf32_pheader)) > data_size) { + logger(LL_ERROR, "%s: Last ELF program section is out of bounds\n", __func__); + return NULL; + } + sect_off = phdr[ehdr->e_phnum-1].p_offset; + sect_size = phdr[ehdr->e_phnum-1].p_filesz; + } else { + const elf32_header* ehdr = data; + const elf32_pheader* phdr = data + ehdr->e_phoff; + if (ehdr->e_phnum == 0) { + logger(LL_ERROR, "%s: ELF has no program sections\n", __func__); + return NULL; + } + if ((ehdr->e_phoff + ehdr->e_phnum * sizeof(elf64_pheader)) > data_size) { + logger(LL_ERROR, "%s: Last ELF program section is out of bounds\n", __func__); + return NULL; + } + sect_off = phdr[ehdr->e_phnum-1].p_offset; + sect_size = phdr[ehdr->e_phnum-1].p_filesz; + } + + if (sect_off == 0) { + logger(LL_ERROR, "%s: section has 0 offset\n", __func__); + return NULL; + } + + if (sect_size == 0) { + logger(LL_ERROR, "%s: section has 0 size\n", __func__); + return NULL; + } + + if (sect_off + sect_size > data_size) { + logger(LL_ERROR, "%s: section (0x%llx+0x%zx) is bigger than the data\n", __func__, sect_off, sect_size); + return NULL; + } + if (sizeof(mbn_v7_header) > sect_size) { + logger(LL_ERROR, "%s: dest header is bigger than the section (0x%zx)\n", __func__, sect_size); + return NULL; + } + + const mbn_v7_header* header = data + sect_off; + mbn_v7_log_header(header, __func__, "dest"); + if (header->version != 7) { + logger(LL_ERROR, "%s: dest header version (0x%x) is incorrect\n", __func__, header->version); + return NULL; + } + + if (!mbn_v7_header_sizes_valid(header, sect_size)) { + logger(LL_ERROR, "%s: sizes in dest header are invalid (common_metadata_size=0x%x, qti_metadata_size=0x%x, oem_metadata_size=0x%x, hash_table_size=0x%x, qti_signature_size=0x%x, qti_certificate_chain_size=0x%x, oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)\n", __func__, header->common_metadata_size, header->qti_metadata_size, header->oem_metadata_size, header->hash_table_size, header->qti_signature_size, header->qti_certificate_chain_size, header->oem_signature_size, header->oem_certificate_chain_size); + return NULL; + } + + if (!mbn_v7_header_sizes_expected(header)) { + logger(LL_WARNING, "%s: header sizes in dest header are unexpected (qti_metadata_size=0x%x, oem_metadata_size=0x%x, oem_signature_size=0x%x, oem_certificate_chain_size=0x%x)\n", __func__, header->qti_metadata_size, header->oem_metadata_size, header->oem_signature_size, header->oem_certificate_chain_size); + } + + size_t new_metadata_size = + sizeof(*src_header) + src_header->common_metadata_size + + src_header->qti_metadata_size + src_header->oem_metadata_size; + size_t new_metadata_and_hash_table_size = + new_metadata_size + src_header->hash_table_size; + size_t new_oem_sig_and_cert_chain_size = + src_header->oem_signature_size + src_header->oem_certificate_chain_size; + off_t new_oem_sig_and_cert_chain_off = new_metadata_and_hash_table_size + + header->qti_signature_size + + header->qti_certificate_chain_size; + + if (new_metadata_and_hash_table_size > blob_size) { + logger(LL_ERROR, "%s: new metadata (0x%zx) and hash table (0x%x) are bigger than the source (0x%zx)\n", __func__, new_metadata_size, src_header->hash_table_size, blob_size); + return NULL; + } + + if (new_metadata_and_hash_table_size > sect_size) { + logger(LL_ERROR, "%s: new metadata (0x%zx) and hash table (0x%x) are bigger than the destination (0x%zx)\n", __func__, new_metadata_size, src_header->hash_table_size, sect_size); + return NULL; + } + + if (new_metadata_and_hash_table_size + new_oem_sig_and_cert_chain_size > blob_size) { + logger(LL_ERROR, "%s: new OEM signature and certificate chain are bigger than the source\n", __func__); + return NULL; + } + + if (new_oem_sig_and_cert_chain_off + new_oem_sig_and_cert_chain_size > sect_size) { + logger(LL_ERROR, "%s: new OEM signature and certificate chain are outside the bounds of the destination\n", __func__); + return NULL; + } + + void* buf = malloc(data_size); + if (buf == NULL) { + logger(LL_ERROR, "out of memory\n"); + return NULL; + } + + memcpy(buf, data, data_size); + logger(LL_DEBUG, "%s: stitching mbn at 0x%llx (0x%zx bytes)\n", __func__, sect_off, new_metadata_and_hash_table_size); + memcpy(buf + sect_off, blob, new_metadata_and_hash_table_size); + logger(LL_DEBUG, "%s: stitching mbn at 0x%llx (0x%zx bytes)\n", __func__, sect_off + new_oem_sig_and_cert_chain_off, new_oem_sig_and_cert_chain_size); + memcpy(buf + sect_off + new_oem_sig_and_cert_chain_off, blob + new_metadata_and_hash_table_size, new_oem_sig_and_cert_chain_size); + + return buf; +} |