diff --git a/librz/bin/format/mtk/md1img.c b/librz/bin/format/mtk/md1img.c new file mode 100644 index 0000000000..e7cac9ca6d --- /dev/null +++ b/librz/bin/format/mtk/md1img.c @@ -0,0 +1,665 @@ +// SPDX-FileCopyrightText: 2025 godcodehunter +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file Parser for MediaTek md1img container format. + * + * The md1img format is a simple section-based container used by MediaTek + * to package modem firmware components: md1rom (GFH firmware image), + * debug info (CATI format), DSP images, certificates, etc. + * + * Each section has a header with magic 0x58881688, a name, data size, + * memory address, and other metadata, followed by the section data + * and alignment padding. + * + * The md1rom section is parsed internally using the mtk GFH parser to + * provide proper base address, entry points, and code/header sections. + * Debug symbols are extracted from the CATI-format dbginfo section. + * + * References: + * - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_img.ksy (md1img) + * - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_dbg_info.ksy (CATI) + */ +#include "md1img.h" + +// --- CATI debug info parsing --- + +static void mtk_dbg_symbol_free(MtkDbgSymbol *sym) { + if (sym) { + free(sym->name); + free(sym); + } +} + +/** + * \brief Parse symbols from a single CATI debug entry. + * + * The debug entry format (after "CATI" magic + type): + * unk3(u32), unk_str1(strz), unk_str2(strz), unk_str3(strz), date_str(strz), + * symbols_start(u32), files_start(u32), + * symbol_entries: [strz name, u32 addr1, u32 addr2] ... until empty name + */ +static bool md1img_parse_cati_debug(RzBuffer *b, RzPVector /**/ *symbols) { + // Skip unk3 + rz_buf_seek(b, 4, RZ_BUF_CUR); + + // Skip 4 strz fields (unk_str1, unk_str2, unk_str3, date_str) + for (int i = 0; i < 4; i++) { + ut64 len = rz_buf_read_string(b, NULL); + if (len == 0) { + return false; + } + } + + // Skip symbols_start and files_start + rz_buf_seek(b, 8, RZ_BUF_CUR); + + // Parse symbol entries (limit guards against corrupted/missing terminator) + int count = 0; + while (count < 500000) { + char *name = NULL; + ut64 len = rz_buf_read_string(b, &name); + if (len == 0 || !name) { + free(name); + break; + } + // Empty string = terminator + if (name[0] == '\0') { + free(name); + break; + } + + ut32 addr1 = 0, addr2 = 0; + if (!rz_buf_read_le32(b, &addr1) || !rz_buf_read_le32(b, &addr2)) { + free(name); + break; + } + + MtkDbgSymbol *sym = RZ_NEW0(MtkDbgSymbol); + if (!sym) { + free(name); + break; + } + sym->name = name; + sym->addr = addr1; + // For corrupted entries + sym->size = (addr2 > addr1) ? (addr2 - addr1) : 0; + rz_pvector_push(symbols, sym); + count++; + } + + return count > 0; +} + +/** + * \brief Parse a CATI container wrapping one or more debug entries. + */ +static bool md1img_parse_cati_container(RzBuffer *b, RzPVector /**/ *symbols) { + // unk1(u32), size(u32) + ut32 unk1 = 0, container_size = 0; + if (!rz_buf_read_le32(b, &unk1) || !rz_buf_read_le32(b, &container_size)) { + return false; + } + + // 0x10 = CATI header size (magic[4] + type[4] + unk1[4] + size[4]) + if (container_size < 0x10) { + return false; + } + + // The nested entries are within (container_size - 0x10) bytes + ut64 entries_end = rz_buf_tell(b) + (container_size - 0x10); + ut64 buf_size = rz_buf_size(b); + // Clamp to actual buffer size in case container_size is corrupted + if (entries_end > buf_size) { + entries_end = buf_size; + } + + // Parse nested CATI headers + while (rz_buf_tell(b) + 8 <= entries_end) { + ut8 magic[MTK_CATI_MAGIC_SIZE]; + if (rz_buf_read(b, magic, MTK_CATI_MAGIC_SIZE) != MTK_CATI_MAGIC_SIZE || + memcmp(magic, MTK_CATI_MAGIC, MTK_CATI_MAGIC_SIZE) != 0) { + break; + } + + ut32 cati_type = 0; + if (!rz_buf_read_le32(b, &cati_type)) { + break; + } + + if (cati_type != MTK_CATI_TYPE_DEBUG && cati_type != MTK_CATI_TYPE_DEBUG_DSP) { + break; + } + md1img_parse_cati_debug(b, symbols); + } + + return rz_pvector_len(symbols) > 0; +} + +/** + * \brief Parse a CATI debug info buffer and extract symbols. + */ +static RzPVector /**/ *md1img_parse_dbginfo(RzBuffer *b) { + rz_return_val_if_fail(b, NULL); + + ut8 magic[MTK_CATI_MAGIC_SIZE]; + if (rz_buf_read_at(b, 0, magic, MTK_CATI_MAGIC_SIZE) != MTK_CATI_MAGIC_SIZE) { + return NULL; + } + if (memcmp(magic, MTK_CATI_MAGIC, MTK_CATI_MAGIC_SIZE) != 0) { + return NULL; + } + + rz_buf_seek(b, 0 + MTK_CATI_MAGIC_SIZE, RZ_BUF_SET); + + ut32 cati_type = 0; + if (!rz_buf_read_le32(b, &cati_type)) { + return NULL; + } + + RzPVector *symbols = rz_pvector_new((RzPVectorFree)mtk_dbg_symbol_free); + if (!symbols) { + return NULL; + } + + bool ok = false; + if (cati_type == MTK_CATI_TYPE_CONTAINER) { + ok = md1img_parse_cati_container(b, symbols); + } else if (cati_type == MTK_CATI_TYPE_DEBUG || cati_type == MTK_CATI_TYPE_DEBUG_DSP) { + ok = md1img_parse_cati_debug(b, symbols); + } + + if (!ok || rz_pvector_len(symbols) == 0) { + rz_pvector_free(symbols); + return NULL; + } + + return symbols; +} + +// --- md1img container parsing --- + +static bool md1img_read_section_hdr(RzBuffer *b, ut64 section_start, Md1imgSection *sec) { + ut64 offset = section_start; + + // Check magic + ut8 magic[MD1IMG_MAGIC_SIZE]; + if (!rz_buf_read_offset(b, &offset, magic, MD1IMG_MAGIC_SIZE)) { + return false; + } + if (memcmp(magic, MD1IMG_MAGIC, MD1IMG_MAGIC_SIZE) != 0) { + return false; + } + + // Read dsize + if (!rz_buf_read_le32_offset(b, &offset, &sec->dsize)) { + return false; + } + + // Read name (32 bytes, null-terminated) + if (!rz_buf_read_offset(b, &offset, (ut8 *)sec->name, MD1IMG_NAME_SIZE)) { + return false; + } + sec->name[MD1IMG_NAME_SIZE - 1] = '\0'; + + // Read maddr and mode + if (!rz_buf_read_le32_offset(b, &offset, &sec->maddr) || + !rz_buf_read_le32_offset(b, &offset, &sec->mode)) { + return false; + } + + // Verify ext_magic + ut8 ext_magic[MD1IMG_EXT_MAGIC_SIZE]; + if (!rz_buf_read_offset(b, &offset, ext_magic, MD1IMG_EXT_MAGIC_SIZE)) { + return false; + } + if (memcmp(ext_magic, MD1IMG_EXT_MAGIC, MD1IMG_EXT_MAGIC_SIZE) != 0) { + return false; + } + + // Read remaining header fields + if (!rz_buf_read_le32_offset(b, &offset, &sec->hdr_size) || + !rz_buf_read_le32_offset(b, &offset, &sec->hdr_version) || + !rz_buf_read_le32_offset(b, &offset, &sec->img_type) || + !rz_buf_read_le32_offset(b, &offset, &sec->img_list_end) || + !rz_buf_read_le32_offset(b, &offset, &sec->align_size) || + !rz_buf_read_le32_offset(b, &offset, &sec->dsize_extend) || + !rz_buf_read_le32_offset(b, &offset, &sec->maddr_extend)) { + return false; + } + + if (sec->hdr_size < MD1IMG_MIN_HDR_SIZE) { + return false; + } + + sec->data_offset = section_start + sec->hdr_size; + return true; +} + +RZ_IPI bool md1img_check_buffer(RZ_BORROW RZ_NONNULL RzBuffer *b) { + rz_return_val_if_fail(b, false); + + if (rz_buf_size(b) < MD1IMG_MIN_HDR_SIZE) { + return false; + } + + ut8 magic[MD1IMG_MAGIC_SIZE]; + if (rz_buf_read_at(b, 0, magic, MD1IMG_MAGIC_SIZE) != MD1IMG_MAGIC_SIZE) { + return false; + } + return memcmp(magic, MD1IMG_MAGIC, MD1IMG_MAGIC_SIZE) == 0; +} + +/** + * \brief Register the section's virtual file and run per-section parsers + * (GFH for md1rom, CATI for dbginfo). Takes ownership of \p vbuf. + */ +static void md1img_load_section(Md1imgObj *md1, int sec_idx, RZ_BORROW RZ_NONNULL const Md1imgSection *sec, RZ_OWN RZ_NONNULL RzBuffer *vbuf) { + RzBinVirtualFile *vfile = RZ_NEW0(RzBinVirtualFile); + if (!vfile) { + rz_buf_free(vbuf); + return; + } + vfile->buf = vbuf; + vfile->buf_owned = true; + vfile->name = rz_str_dup(sec->name); + rz_pvector_push(md1->vfiles, vfile); + + // Parse md1rom section with mtk GFH parser + if (rz_str_casestr(sec->name, "md1rom") && md1->md1rom_idx < 0) { + md1->md1rom_idx = sec_idx; + // Search for GFH magic within md1rom data (may not be at offset 0) + ut64 gfh_off = 0; + ut8 magic_buf[4]; + while (gfh_off + MTK_GFH_MIN_FILE_SIZE <= sec->dsize) { + if (rz_buf_read_at(vbuf, gfh_off, magic_buf, 4) != 4) { + break; + } + ut32 magic_val = rz_read_le32(magic_buf); + if ((magic_val & MTK_GFH_MAGIC_MASK) == MTK_GFH_MAGIC) { + break; + } + gfh_off += 4; + } + if (gfh_off + MTK_GFH_MIN_FILE_SIZE <= sec->dsize) { + RzBuffer *gfh_buf = rz_buf_new_slice(vbuf, gfh_off, sec->dsize - gfh_off); + if (gfh_buf) { + md1->mtk = mtk_obj_new(gfh_buf); + rz_buf_free(gfh_buf); + } + if (md1->mtk) { + md1->gfh_offset = gfh_off; + RZ_LOG_INFO("md1img: parsed GFH from '%s' at offset 0x%" PFMT64x " (load_addr=0x%x, entry=0x%x)\n", + sec->name, gfh_off, md1->mtk->file_info.load_addr, md1->mtk->entry_vaddr); + } + } + } + + // Parse CATI debug info from dbginfo sections + if (rz_str_casestr(sec->name, "dbginfo") && sec->dsize > MTK_CATI_MAGIC_SIZE) { + RzPVector *syms = md1img_parse_dbginfo(vbuf); + // Try LZMA alone decompression if raw CATI parsing failed (dbginfo may be LZMA-compressed) + if (!syms) { + RzBuffer *decompressed = rz_buf_new_empty(0); + if (decompressed && rz_lzma_alone_dec_buf(vbuf, decompressed, 4096)) { + syms = md1img_parse_dbginfo(decompressed); + } + rz_buf_free(decompressed); + } + if (syms) { + if (!md1->dbg_symbols) { + md1->dbg_symbols = syms; + } else { + // Merge symbols from additional dbginfo sections + void **it; + rz_pvector_foreach (syms, it) { + rz_pvector_push(md1->dbg_symbols, *it); + } + // Disown elements before freeing (ownership transferred) + syms->v.free = NULL; + rz_pvector_free(syms); + } + RZ_LOG_INFO("md1img: loaded debug symbols from '%s'\n", sec->name); + } + } +} + +RZ_IPI bool md1img_load_buffer(RZ_BORROW RZ_NONNULL RzBinFile *bf, RZ_BORROW RZ_NONNULL RzBinObject *obj, RZ_BORROW RZ_NONNULL RzBuffer *b, RZ_BORROW RZ_NULLABLE Sdb *sdb) { + rz_return_val_if_fail(bf && obj && b, false); + + Md1imgObj *md1 = RZ_NEW0(Md1imgObj); + if (!md1) { + return false; + } + + md1->sections = rz_vector_new(sizeof(Md1imgSection), NULL, NULL); + md1->vfiles = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free); + md1->md1rom_idx = -1; + + if (!md1->sections || !md1->vfiles) { + rz_vector_free(md1->sections); + rz_pvector_free(md1->vfiles); + free(md1); + return false; + } + + ut64 buf_size = rz_buf_size(b); + ut64 offset = 0; + int count = 0; + + // Limit iteration count as a safeguard against corrupted headers + while (offset + MD1IMG_MIN_HDR_SIZE <= buf_size && count < 256) { + Md1imgSection sec = { 0 }; + if (!md1img_read_section_hdr(b, offset, &sec)) { + break; + } + + // Validate that data fits in the buffer + if (sec.data_offset + sec.dsize > buf_size) { + RZ_LOG_WARN("md1img: section '%s' data exceeds file size, truncating\n", sec.name); + sec.dsize = buf_size - sec.data_offset; + } + + int sec_idx = rz_vector_len(md1->sections); + rz_vector_push(md1->sections, &sec); + + // Create a virtual file for this section's data and parse it + RzBuffer *vbuf = rz_buf_new_slice(b, sec.data_offset, sec.dsize); + if (vbuf) { + md1img_load_section(md1, sec_idx, &sec, vbuf); + } + + // Advance to next section: data_offset + dsize + alignment padding + ut64 data_end = sec.data_offset + sec.dsize; + ut64 remainder = sec.align_size > 0 ? sec.dsize % sec.align_size : 0; + if (remainder > 0) { + offset = data_end + (sec.align_size - remainder); + } else { + offset = data_end; + } + count++; + } + + if (offset < buf_size) { + RZ_LOG_WARN("md1img: %" PFMT64u " trailing bytes after last section header\n", buf_size - offset); + } + + if (rz_vector_len(md1->sections) == 0) { + rz_vector_free(md1->sections); + rz_pvector_free(md1->vfiles); + free(md1); + return false; + } + + obj->bin_obj = md1; + return true; +} + +RZ_IPI void md1img_destroy(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + if (!bf || !bf->o || !bf->o->bin_obj) { + return; + } + Md1imgObj *md1 = bf->o->bin_obj; + rz_vector_free(md1->sections); + rz_pvector_free(md1->vfiles); + rz_pvector_free(md1->dbg_symbols); + mtk_obj_free(md1->mtk); + free(md1); +} + +RZ_IPI RZ_OWN RzBinInfo *md1img_info(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + RzBinInfo *info = RZ_NEW0(RzBinInfo); + if (!info) { + return NULL; + } + + info->file = bf->file ? rz_str_dup(bf->file) : NULL; + info->type = rz_str_dup("MediaTek md1img container"); + info->machine = rz_str_dup("MediaTek Modem"); + info->arch = rz_str_dup("mips"); + info->cpu = rz_str_dup("nanomips"); + info->rclass = rz_str_dup("firmware"); + info->subsystem = rz_str_dup("modem"); + info->has_va = true; + info->bits = 32; + info->big_endian = false; + + return info; +} + +RZ_IPI ut64 md1img_baddr(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + return MTK_MODEM_BADDR; +} + +RZ_IPI RZ_OWN RzPVector /**/ *md1img_entries(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + Md1imgObj *md1 = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new(free); + if (!ret) { + return NULL; + } + + if (md1->mtk) { + RzBinAddr *entry = RZ_NEW0(RzBinAddr); + if (entry) { + entry->vaddr = md1img_baddr(bf) + (md1->mtk->file_info.jump_offset - md1->mtk->code_offset); + entry->paddr = md1->gfh_offset + md1->mtk->file_info.jump_offset; + rz_pvector_push(ret, entry); + } + } + + return ret; +} + +RZ_IPI RZ_OWN RzPVector /**/ *md1img_virtual_files(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + Md1imgObj *md1 = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_virtual_file_free); + if (!ret) { + return NULL; + } + + void **it; + rz_pvector_foreach (md1->vfiles, it) { + RzBinVirtualFile *vf = *it; + RzBinVirtualFile *clone = rz_bin_virtual_file_clone(vf); + if (clone) { + rz_pvector_push(ret, clone); + } + } + + return ret; +} + +RZ_IPI RZ_OWN RzPVector /**/ *md1img_maps(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + Md1imgObj *md1 = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_map_free); + if (!ret) { + return NULL; + } + + // Only map the md1rom section into the virtual address space. + // Other sections (md1dsp, md1drdi, certs, etc.) belong to different + // processors or address spaces and would conflict with md1rom if mapped. + // They remain accessible as virtual files for raw data viewing. + if (md1->md1rom_idx >= 0) { + Md1imgSection *sec = rz_vector_index_ptr(md1->sections, md1->md1rom_idx); + if (md1->mtk) { + ut64 paddr = md1->gfh_offset + md1->mtk->code_offset; + mtk_append_maps(md1->mtk, paddr, sec->name, ret); + } else { + RzBinMap *map = RZ_NEW0(RzBinMap); + if (map) { + map->name = rz_str_dup(sec->name); + map->vfile_name = rz_str_dup(sec->name); + map->paddr = 0; + map->psize = sec->dsize; + map->vaddr = sec->maddr; + map->vsize = sec->dsize; + map->perm = RZ_PERM_RX; + rz_pvector_push(ret, map); + } + } + } + + return ret; +} + +RZ_IPI RZ_OWN RzPVector /**/ *md1img_sections(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + Md1imgObj *md1 = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_section_free); + if (!ret) { + return NULL; + } + + if (md1->mtk && md1->md1rom_idx >= 0 && md1->mtk->code_size > 0) { + Md1imgSection *sec = rz_vector_index_ptr(md1->sections, md1->md1rom_idx); + RzBinSection *code = RZ_NEW0(RzBinSection); + if (code) { + code->name = rz_str_newf("%s.code", sec->name); + code->paddr = md1->gfh_offset + md1->mtk->code_offset; + code->size = md1->mtk->code_size; + code->vsize = md1->mtk->code_size; + code->vaddr = md1img_baddr(bf); + code->perm = RZ_PERM_RX; + rz_pvector_push(ret, code); + } + } + + return ret; +} + +RZ_IPI RZ_OWN RzPVector /**/ *md1img_symbols(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + Md1imgObj *md1 = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_symbol_free); + if (!ret) { + return NULL; + } + + if (!md1->dbg_symbols) { + return ret; + } + + void **it; + rz_pvector_foreach (md1->dbg_symbols, it) { + MtkDbgSymbol *dbg = *it; + RzBinSymbol *sym = RZ_NEW0(RzBinSymbol); + if (!sym) { + continue; + } + sym->name = rz_str_dup(dbg->name); + sym->vaddr = dbg->addr; + sym->size = dbg->size; + sym->type = RZ_BIN_TYPE_FUNC_STR; + + // Compute paddr for symbols within mapped regions (kseg0 or kuseg). + if (md1->mtk) { + ut64 code_paddr = md1->gfh_offset + md1->mtk->code_offset; + ut64 kseg0_end = md1img_baddr(bf) + md1->mtk->code_size; + ut64 kuseg_base = md1->mtk->file_info.load_addr + md1->mtk->code_offset; + ut64 kuseg_end = kuseg_base + md1->mtk->code_size; + if (dbg->addr >= md1img_baddr(bf) && dbg->addr < kseg0_end) { + sym->paddr = code_paddr + (dbg->addr - md1img_baddr(bf)); + } else if (dbg->addr >= kuseg_base && dbg->addr < kuseg_end) { + sym->paddr = code_paddr + (dbg->addr - kuseg_base); + } + } + + rz_pvector_push(ret, sym); + } + + return ret; +} + +RZ_IPI RZ_OWN RzPVector /**/ *md1img_strings(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + // Container format: suppress string scanning on the raw container buffer. + // Strings from the firmware payload are accessible via the mapped vfiles. + return rz_pvector_new(NULL); +} + +RZ_IPI RZ_OWN RzStructuredData *md1img_structure(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + Md1imgObj *md1 = bf->o->bin_obj; + RzStructuredData *root = rz_structured_data_new_map(); + if (!root) { + return NULL; + } + + rz_structured_data_map_add_unsigned(root, "num_sections", rz_vector_len(md1->sections), false); + + RzStructuredData *secs = rz_structured_data_map_add_array(root, "sections"); + if (secs) { + Md1imgSection *sec; + rz_vector_foreach (md1->sections, sec) { + RzStructuredData *s = rz_structured_data_array_add_map(secs); + if (!s) { + continue; + } + rz_structured_data_map_add_string(s, "name", sec->name); + rz_structured_data_map_add_unsigned(s, "data_size", sec->dsize, true); + rz_structured_data_map_add_unsigned(s, "data_offset", sec->data_offset, true); + rz_structured_data_map_add_unsigned(s, "maddr", sec->maddr, true); + rz_structured_data_map_add_unsigned(s, "mode", sec->mode, true); + rz_structured_data_map_add_unsigned(s, "hdr_size", sec->hdr_size, true); + rz_structured_data_map_add_unsigned(s, "hdr_version", sec->hdr_version, true); + rz_structured_data_map_add_unsigned(s, "img_type", sec->img_type, true); + rz_structured_data_map_add_unsigned(s, "img_list_end", sec->img_list_end, true); + rz_structured_data_map_add_unsigned(s, "align_size", sec->align_size, true); + } + } + + // GFH structure from md1rom section + if (md1->mtk) { + MtkObj *mtk = md1->mtk; + RzStructuredData *gfh = rz_structured_data_map_add_map(root, "gfh_file_info"); + if (gfh) { + rz_structured_data_map_add_unsigned(gfh, "magic_version", mtk->first_common.magic_version, true); + rz_structured_data_map_add_unsigned(gfh, "header_block_size", mtk->first_common.size, false); + rz_structured_data_map_add_string(gfh, "header_type", mtk_gfh_type_str(mtk->first_common.type)); + rz_structured_data_map_add_string(gfh, "name", mtk->file_info.name); + rz_structured_data_map_add_unsigned(gfh, "file_type", mtk->file_info.file_type, true); + rz_structured_data_map_add_unsigned(gfh, "flash_type", mtk->file_info.flash_type, true); + rz_structured_data_map_add_unsigned(gfh, "sig_type", mtk->file_info.sig_type, true); + rz_structured_data_map_add_unsigned(gfh, "load_addr", mtk->file_info.load_addr, true); + rz_structured_data_map_add_unsigned(gfh, "total_size", mtk->file_info.total_size, true); + rz_structured_data_map_add_unsigned(gfh, "max_size", mtk->file_info.max_size, true); + rz_structured_data_map_add_unsigned(gfh, "hdr_size", mtk->file_info.hdr_size, true); + rz_structured_data_map_add_unsigned(gfh, "sig_size", mtk->file_info.sig_size, true); + rz_structured_data_map_add_unsigned(gfh, "jump_offset", mtk->file_info.jump_offset, true); + rz_structured_data_map_add_unsigned(gfh, "entry_point", mtk->entry_vaddr, true); + } + + if (rz_vector_len(mtk->extra_headers) > 0) { + RzStructuredData *hdrs = rz_structured_data_map_add_array(root, "gfh_headers"); + if (hdrs) { + MtkGfhHeader *extra; + rz_vector_foreach (mtk->extra_headers, extra) { + RzStructuredData *h = rz_structured_data_array_add_map(hdrs); + if (!h) { + continue; + } + rz_structured_data_map_add_unsigned(h, "file_offset", extra->file_offset, true); + rz_structured_data_map_add_string(h, "type", mtk_gfh_type_str(extra->common.type)); + rz_structured_data_map_add_unsigned(h, "type_id", extra->common.type, true); + rz_structured_data_map_add_unsigned(h, "size", extra->common.size, false); + } + } + } + } + + if (md1->dbg_symbols) { + rz_structured_data_map_add_unsigned(root, "debug_symbols_count", + rz_pvector_len(md1->dbg_symbols), false); + } + + return root; +} diff --git a/librz/bin/format/mtk/md1img.h b/librz/bin/format/mtk/md1img.h new file mode 100644 index 0000000000..f306891c97 --- /dev/null +++ b/librz/bin/format/mtk/md1img.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2025 godcodehunter +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file Header for MediaTek md1img container format parser. + * + * The md1img format packages modem firmware components: md1rom (GFH image), + * debug info (CATI format), DSP images, certificates, etc. + * + * References: + * - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_img.ksy (md1img) + * - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_dbg_info.ksy (CATI) + */ + +#ifndef MD1IMG_H +#define MD1IMG_H + +#include +#include +#include +#include + +#include "mtk.h" + +#define MD1IMG_MAGIC "\x88\x16\x88\x58" +#define MD1IMG_MAGIC_SIZE 4 +#define MD1IMG_EXT_MAGIC "\x89\x16\x89\x58" +#define MD1IMG_EXT_MAGIC_SIZE 4 +#define MD1IMG_NAME_SIZE 32 +/* Minimum section header size (fields before reserved) */ +#define MD1IMG_MIN_HDR_SIZE 0x50 + +#define MTK_CATI_MAGIC "CATI" +#define MTK_CATI_MAGIC_SIZE 4 +/* "CTNR" as LE u32 */ +#define MTK_CATI_TYPE_CONTAINER 0x524E5443 +#define MTK_CATI_TYPE_DEBUG 1 +#define MTK_CATI_TYPE_DEBUG_DSP 2 + +/** + * \brief A debug symbol parsed from a CATI debug info section. + * + * Reference: https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_dbg_info.ksy + */ +typedef struct mtk_dbg_symbol { + char *name; ///< Symbol name + ut32 addr; ///< Symbol address + ut32 size; ///< Symbol size (addr2 - addr1, or 0 if addr2 <= addr1) +} MtkDbgSymbol; + +/** + * \brief Parsed section from an md1img container. + * + * Each section has a header (magic + metadata) followed by data and + * alignment padding. + */ +typedef struct md1img_section { + char name[MD1IMG_NAME_SIZE]; ///< Section name (null-terminated) + ut32 dsize; ///< Data size + ut32 maddr; ///< Memory address + ut32 mode; ///< Mode flags + ut32 hdr_size; ///< Header size (total, from start of section to data) + ut32 hdr_version; ///< Header version + ut32 img_type; ///< Image type + ut32 img_list_end; ///< Image list end marker + ut32 align_size; ///< Alignment size for padding after data + ut32 dsize_extend; ///< Extended data size (high bits) + ut32 maddr_extend; ///< Extended memory address (high bits) + ut64 data_offset; ///< File offset where section data starts +} Md1imgSection; + +/** + * \brief Top-level parsed object for md1img container. + */ +typedef struct md1img_obj { + RzVector /**/ *sections; ///< All parsed container sections + RzPVector /**/ *vfiles; ///< Virtual file per section + RzPVector /**/ *dbg_symbols; ///< Debug symbols from CATI section + MtkObj *mtk; ///< Parsed GFH from md1rom section (or NULL) + ut64 gfh_offset; ///< Offset of GFH within md1rom vfile + int md1rom_idx; ///< Index of md1rom section, or -1 +} Md1imgObj; + +RZ_IPI bool md1img_check_buffer(RZ_BORROW RZ_NONNULL RzBuffer *b); +RZ_IPI bool md1img_load_buffer(RZ_BORROW RZ_NONNULL RzBinFile *bf, RZ_BORROW RZ_NONNULL RzBinObject *obj, RZ_BORROW RZ_NONNULL RzBuffer *b, RZ_BORROW RZ_NULLABLE Sdb *sdb); +RZ_IPI void md1img_destroy(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzBinInfo *md1img_info(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI ut64 md1img_baddr(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *md1img_entries(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *md1img_virtual_files(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *md1img_maps(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *md1img_sections(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *md1img_symbols(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *md1img_strings(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzStructuredData *md1img_structure(RZ_BORROW RZ_NONNULL RzBinFile *bf); + +#endif diff --git a/librz/bin/format/mtk/mtk.c b/librz/bin/format/mtk/mtk.c new file mode 100644 index 0000000000..61f0a399ac --- /dev/null +++ b/librz/bin/format/mtk/mtk.c @@ -0,0 +1,416 @@ +// SPDX-FileCopyrightText: 2025 godcodehunter +// SPDX-License-Identifier: LGPL-3.0-only + +/** + * \file Implementation of MediaTek GFH firmware image parser (md1rom). + * + * The GFH (Generic File Header) format is used by MediaTek for bootloader + * and modem firmware images. The file starts with a chain of GFH headers + * (the first must be FILE_INFO), followed by the code/data payload. + * + * References: + * - https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_img.ksy + * - https://github.com/u-boot/u-boot/blob/master/tools/mtk_image.h + */ +#include "mtk.h" +#include "md1img.h" + +static inline bool mtk_is_gfh_magic(ut32 magic_version) { + return (magic_version & MTK_GFH_MAGIC_MASK) == MTK_GFH_MAGIC; +} + +/** + * \brief Scan for GFH FILE_INFO header within the first MTK_GFH_SCAN_LIMIT bytes. + * \return offset of the GFH header, or UT64_MAX if not found. + */ +static ut64 mtk_find_gfh(RzBuffer *b) { + ut64 buf_size = rz_buf_size(b); + ut64 limit = RZ_MIN(buf_size, MTK_GFH_SCAN_LIMIT); + // GFH headers are typically aligned to 4 bytes + for (ut64 off = 0; off + MTK_GFH_MIN_FILE_SIZE <= limit; off += 4) { + ut64 cursor = off; + MtkGfhCommonHdr hdr = { 0 }; + if (!rz_buf_read_le32_offset(b, &cursor, &hdr.magic_version) || + !rz_buf_read_le16_offset(b, &cursor, &hdr.size) || + !rz_buf_read_le16_offset(b, &cursor, (ut16 *)&hdr.type)) { + continue; + } + if (mtk_is_gfh_magic(hdr.magic_version) && + hdr.type == MTK_GFH_TYPE_FILE_INFO && + hdr.size >= MTK_GFH_MIN_FILE_SIZE) { + return off; + } + } + return UT64_MAX; +} + +static bool mtk_read_common_hdr(RzBuffer *b, ut64 *offset, MtkGfhCommonHdr *hdr) { + return rz_buf_read_le32_offset(b, offset, &hdr->magic_version) && + rz_buf_read_le16_offset(b, offset, &hdr->size) && + rz_buf_read_le16_offset(b, offset, (ut16 *)&hdr->type); +} + +static bool mtk_read_file_info(RzBuffer *b, ut64 *offset, MtkGfhFileInfo *fi) { + if (!rz_buf_read_offset(b, offset, (ut8 *)fi->name, MTK_GFH_FILE_INFO_NAME_SIZE)) { + return false; + } + fi->name[MTK_GFH_FILE_INFO_NAME_SIZE - 1] = '\0'; + return rz_buf_read_le32_offset(b, offset, &fi->unused) && + rz_buf_read_le16_offset(b, offset, &fi->file_type) && + rz_buf_read8_offset(b, offset, &fi->flash_type) && + rz_buf_read8_offset(b, offset, &fi->sig_type) && + rz_buf_read_le32_offset(b, offset, &fi->load_addr) && + rz_buf_read_le32_offset(b, offset, &fi->total_size) && + rz_buf_read_le32_offset(b, offset, &fi->max_size) && + rz_buf_read_le32_offset(b, offset, &fi->hdr_size) && + rz_buf_read_le32_offset(b, offset, &fi->sig_size) && + rz_buf_read_le32_offset(b, offset, &fi->jump_offset) && + rz_buf_read_le32_offset(b, offset, &fi->processed); +} + +RZ_IPI RZ_BORROW const char *mtk_gfh_type_str(MtkGfhType type) { + switch (type) { + case MTK_GFH_TYPE_FILE_INFO: + return "file_info"; + case MTK_GFH_TYPE_BL_INFO: + return "bl_info"; + case MTK_GFH_TYPE_ANTI_CLONE: + return "anti_clone"; + case MTK_GFH_TYPE_BL_SEC_KEY: + return "bl_sec_key"; + case MTK_GFH_TYPE_BROM_CFG: + return "brom_cfg"; + case MTK_GFH_TYPE_BROM_SEC_CFG: + return "brom_sec_cfg"; + case MTK_GFH_TYPE_0x200: + return "type_0x200"; + case MTK_GFH_TYPE_RSA_MAYBE: + return "rsa_maybe"; + default: + return "unknown"; + } +} + +// --- GFH format parsing --- + +RZ_IPI bool mtk_check_buffer(RZ_BORROW RZ_NONNULL RzBuffer *b) { + rz_return_val_if_fail(b, false); + + ut64 buf_size = rz_buf_size(b); + if (buf_size < MTK_GFH_MIN_FILE_SIZE) { + return false; + } + + // Reject md1img containers — they have their own plugin + ut8 first4[MD1IMG_MAGIC_SIZE]; + if (rz_buf_read_at(b, 0, first4, MD1IMG_MAGIC_SIZE) == MD1IMG_MAGIC_SIZE && + memcmp(first4, MD1IMG_MAGIC, MD1IMG_MAGIC_SIZE) == 0) { + return false; + } + + ut64 gfh_off = mtk_find_gfh(b); + if (gfh_off == UT64_MAX) { + return false; + } + + ut64 offset = gfh_off + MTK_GFH_COMMON_HDR_SIZE; + MtkGfhFileInfo fi; + if (!mtk_read_file_info(b, &offset, &fi)) { + return false; + } + + if (fi.hdr_size < MTK_GFH_MIN_FILE_SIZE || gfh_off + fi.hdr_size > buf_size) { + return false; + } + if (fi.load_addr == 0) { + return false; + } + + return true; +} + +RZ_IPI RZ_OWN MtkObj *mtk_obj_new(RZ_BORROW RZ_NONNULL RzBuffer *b) { + rz_return_val_if_fail(b, NULL); + + MtkObj *mtk = RZ_NEW0(MtkObj); + if (!mtk) { + return NULL; + } + + mtk->extra_headers = rz_vector_new(sizeof(MtkGfhHeader), NULL, NULL); + if (!mtk->extra_headers) { + free(mtk); + return NULL; + } + + ut64 gfh_off = mtk_find_gfh(b); + if (gfh_off == UT64_MAX) { + goto fail; + } + mtk->gfh_offset = gfh_off; + + ut64 offset = gfh_off; + if (!mtk_read_common_hdr(b, &offset, &mtk->first_common)) { + goto fail; + } + if (!mtk_read_file_info(b, &offset, &mtk->file_info)) { + goto fail; + } + + ut64 buf_size = rz_buf_size(b); + if (gfh_off + mtk->file_info.hdr_size > buf_size) { + RZ_LOG_ERROR("MTK: header size (0x%x) exceeds file size\n", mtk->file_info.hdr_size); + goto fail; + } + + mtk->code_offset = gfh_off + mtk->file_info.hdr_size; + if (buf_size > mtk->code_offset) { + mtk->code_size = buf_size - mtk->code_offset; + } + // jump_offset is relative to the GFH start + ut32 code_offset_relative = mtk->file_info.hdr_size; + mtk->entry_vaddr = MTK_MODEM_BADDR + (mtk->file_info.jump_offset - code_offset_relative); + + // Parse additional GFH headers between the first header and the code area + offset = gfh_off + mtk->first_common.size; + int count = 0; + while (offset + MTK_GFH_COMMON_HDR_SIZE <= mtk->code_offset && count < 100) { + ut64 hdr_start = offset; + MtkGfhCommonHdr extra_common = { 0 }; + if (!mtk_read_common_hdr(b, &offset, &extra_common)) { + break; + } + if (!mtk_is_gfh_magic(extra_common.magic_version)) { + break; + } + if (extra_common.size < MTK_GFH_COMMON_HDR_SIZE) { + break; + } + + MtkGfhHeader entry = { + .common = extra_common, + .file_offset = hdr_start, + }; + rz_vector_push(mtk->extra_headers, &entry); + + offset = hdr_start + extra_common.size; + count++; + } + + return mtk; + +fail: + rz_vector_free(mtk->extra_headers); + free(mtk); + return NULL; +} + +RZ_IPI void mtk_obj_free(RZ_OWN RZ_NULLABLE MtkObj *mtk) { + if (!mtk) { + return; + } + rz_vector_free(mtk->extra_headers); + free(mtk); +} + +RZ_IPI bool mtk_load_buffer(RZ_BORROW RZ_NONNULL RzBinFile *bf, RZ_BORROW RZ_NONNULL RzBinObject *obj, RZ_BORROW RZ_NONNULL RzBuffer *b, RZ_BORROW RZ_NULLABLE Sdb *sdb) { + rz_return_val_if_fail(bf && obj && b, false); + MtkObj *mtk = mtk_obj_new(b); + if (!mtk) { + return false; + } + obj->bin_obj = mtk; + return true; +} + +RZ_IPI void mtk_destroy(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + if (!bf || !bf->o || !bf->o->bin_obj) { + return; + } + mtk_obj_free(bf->o->bin_obj); +} + +RZ_IPI ut64 mtk_baddr(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + return MTK_MODEM_BADDR; +} + +RZ_IPI RZ_OWN RzPVector /**/ *mtk_entries(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + MtkObj *mtk = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new(free); + if (!ret) { + return NULL; + } + + RzBinAddr *entry = RZ_NEW0(RzBinAddr); + if (entry) { + entry->paddr = mtk->gfh_offset + mtk->file_info.jump_offset; + entry->vaddr = mtk->entry_vaddr; + rz_pvector_push(ret, entry); + } + + return ret; +} + +RZ_IPI bool mtk_append_maps(RZ_BORROW RZ_NONNULL MtkObj *mtk, ut64 paddr, RZ_BORROW RZ_NULLABLE const char *name, RZ_BORROW RZ_NONNULL RzPVector /**/ *ret) { + rz_return_val_if_fail(mtk && ret, false); + if (mtk->code_size == 0) { + return true; + } + + // kseg0 map: main code at runtime virtual address + RzBinMap *kseg0 = RZ_NEW0(RzBinMap); + if (!kseg0) { + return false; + } + kseg0->name = name ? rz_str_dup(name) : rz_str_dup("code"); + kseg0->vfile_name = name ? rz_str_dup(name) : NULL; + kseg0->paddr = paddr; + kseg0->psize = mtk->code_size; + kseg0->vaddr = MTK_MODEM_BADDR; + kseg0->vsize = mtk->code_size; + kseg0->perm = RZ_PERM_RX; + rz_pvector_push(ret, kseg0); + + // kuseg map: same code at physical addresses (ERL=1 identity mapping). + // The boot code uses kuseg addresses before clearing ERL in Status. + RzBinMap *kuseg = RZ_NEW0(RzBinMap); + if (!kuseg) { + return false; + } + kuseg->name = name ? rz_str_newf("%s.kuseg", name) : rz_str_dup("code.kuseg"); + kuseg->vfile_name = name ? rz_str_dup(name) : NULL; + kuseg->paddr = paddr; + kuseg->psize = mtk->code_size; + kuseg->vaddr = (ut64)mtk->file_info.load_addr + mtk->file_info.hdr_size; + kuseg->vsize = mtk->code_size; + kuseg->perm = RZ_PERM_RX; + rz_pvector_push(ret, kuseg); + + return true; +} + +RZ_IPI RZ_OWN RzPVector /**/ *mtk_maps(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + MtkObj *mtk = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_map_free); + if (!ret) { + return NULL; + } + + mtk_append_maps(mtk, mtk->code_offset, NULL, ret); + return ret; +} + +RZ_IPI RZ_OWN RzPVector /**/ *mtk_sections(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + MtkObj *mtk = bf->o->bin_obj; + RzPVector *ret = rz_pvector_new((RzPVectorFree)rz_bin_section_free); + if (!ret) { + return NULL; + } + + // Header section + RzBinSection *hdr_section = RZ_NEW0(RzBinSection); + if (hdr_section) { + hdr_section->name = rz_str_dup("header"); + hdr_section->paddr = 0; + hdr_section->size = mtk->code_offset; + hdr_section->vsize = mtk->code_offset; + hdr_section->vaddr = 0; + hdr_section->perm = RZ_PERM_R; + rz_pvector_push(ret, hdr_section); + } + + // Code section + if (mtk->code_size > 0) { + RzBinSection *code_section = RZ_NEW0(RzBinSection); + if (code_section) { + code_section->name = rz_str_dup("code"); + code_section->paddr = mtk->code_offset; + code_section->size = mtk->code_size; + code_section->vsize = mtk->code_size; + code_section->vaddr = md1img_baddr(bf); + code_section->perm = RZ_PERM_RX; + rz_pvector_push(ret, code_section); + } + } + + return ret; +} + +RZ_IPI RZ_OWN RzBinInfo *mtk_info(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + RzBinInfo *info = RZ_NEW0(RzBinInfo); + if (!info) { + return NULL; + } + + info->file = bf->file ? rz_str_dup(bf->file) : NULL; + info->type = rz_str_dup("MediaTek GFH"); + info->machine = rz_str_dup("MediaTek Modem"); + info->arch = rz_str_dup("mips"); + info->cpu = rz_str_dup("nanomips"); + info->rclass = rz_str_dup("firmware"); + info->subsystem = rz_str_dup("modem"); + info->has_va = true; + info->bits = 32; + info->big_endian = false; + + return info; +} + +RZ_IPI RZ_OWN RzStructuredData *mtk_structure(RZ_BORROW RZ_NONNULL RzBinFile *bf) { + rz_return_val_if_fail(bf && bf->o && bf->o->bin_obj, NULL); + + MtkObj *mtk = bf->o->bin_obj; + RzStructuredData *root = rz_structured_data_new_map(); + if (!root) { + return NULL; + } + + // File info header + RzStructuredData *fi = rz_structured_data_map_add_map(root, "gfh_file_info"); + if (fi) { + rz_structured_data_map_add_unsigned(fi, "magic_version", mtk->first_common.magic_version, true); + rz_structured_data_map_add_unsigned(fi, "header_block_size", mtk->first_common.size, false); + rz_structured_data_map_add_string(fi, "header_type", mtk_gfh_type_str(mtk->first_common.type)); + rz_structured_data_map_add_string(fi, "name", mtk->file_info.name); + rz_structured_data_map_add_unsigned(fi, "file_type", mtk->file_info.file_type, true); + rz_structured_data_map_add_unsigned(fi, "flash_type", mtk->file_info.flash_type, true); + rz_structured_data_map_add_unsigned(fi, "sig_type", mtk->file_info.sig_type, true); + rz_structured_data_map_add_unsigned(fi, "load_addr", mtk->file_info.load_addr, true); + rz_structured_data_map_add_unsigned(fi, "total_size", mtk->file_info.total_size, true); + rz_structured_data_map_add_unsigned(fi, "max_size", mtk->file_info.max_size, true); + rz_structured_data_map_add_unsigned(fi, "hdr_size", mtk->file_info.hdr_size, true); + rz_structured_data_map_add_unsigned(fi, "sig_size", mtk->file_info.sig_size, true); + rz_structured_data_map_add_unsigned(fi, "jump_offset", mtk->file_info.jump_offset, true); + rz_structured_data_map_add_unsigned(fi, "entry_point", mtk->entry_vaddr, true); + } + + // Extra GFH headers + if (rz_vector_len(mtk->extra_headers) > 0) { + RzStructuredData *hdrs = rz_structured_data_map_add_array(root, "gfh_headers"); + if (hdrs) { + MtkGfhHeader *extra; + rz_vector_foreach (mtk->extra_headers, extra) { + RzStructuredData *h = rz_structured_data_array_add_map(hdrs); + if (!h) { + continue; + } + rz_structured_data_map_add_unsigned(h, "file_offset", extra->file_offset, true); + rz_structured_data_map_add_string(h, "type", mtk_gfh_type_str(extra->common.type)); + rz_structured_data_map_add_unsigned(h, "type_id", extra->common.type, true); + rz_structured_data_map_add_unsigned(h, "size", extra->common.size, false); + } + } + } + + // Derived values + rz_structured_data_map_add_unsigned(root, "code_offset", mtk->code_offset, true); + rz_structured_data_map_add_unsigned(root, "code_size", mtk->code_size, true); + + return root; +} diff --git a/librz/bin/format/mtk/mtk.h b/librz/bin/format/mtk/mtk.h new file mode 100644 index 0000000000..8b36ecb201 --- /dev/null +++ b/librz/bin/format/mtk/mtk.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2025 mrsmith +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef MTK_H +#define MTK_H + +#include +#include +#include +#include + +#define MTK_GFH_MAGIC_MASK 0x00FFFFFF +/* "MMM" in lower 3 bytes */ +#define MTK_GFH_MAGIC 0x004D4D4D +#define MTK_GFH_COMMON_HDR_SIZE 8 +#define MTK_GFH_FILE_INFO_BODY_SIZE 48 +#define MTK_GFH_MIN_FILE_SIZE (MTK_GFH_COMMON_HDR_SIZE + MTK_GFH_FILE_INFO_BODY_SIZE) +#define MTK_GFH_FILE_INFO_NAME_SIZE 12 +/* Runtime virtual base address for modem code */ +#define MTK_MODEM_BADDR 0x90000000ULL + +typedef enum { + MTK_GFH_TYPE_FILE_INFO = 0x0000, + MTK_GFH_TYPE_BL_INFO = 0x0001, + MTK_GFH_TYPE_ANTI_CLONE = 0x0002, + MTK_GFH_TYPE_BL_SEC_KEY = 0x0003, + MTK_GFH_TYPE_BROM_CFG = 0x0007, + MTK_GFH_TYPE_BROM_SEC_CFG = 0x0008, + MTK_GFH_TYPE_0x200 = 0x0200, + MTK_GFH_TYPE_RSA_MAYBE = 0x0202, +} MtkGfhType; + +/** + * \brief GFH Common Header (8 bytes, present in every GFH block). + * + * MediaTek Generic File Header format. The magic_version field encodes + * "MMM" (0x4D4D4D) in the lower 3 bytes and a version number in the + * upper byte. + * + * Reference: https://github.com/u-boot/u-boot/blob/master/tools/mtk_image.h + */ +typedef struct mtk_gfh_common_hdr { + ut32 magic_version; ///< Lower 3 bytes = "MMM" (0x4D4D4D), upper byte = version + ut16 size; ///< Total size of this header block (common + body) + MtkGfhType type; ///< Header type; on-disk wire size is 2 bytes +} MtkGfhCommonHdr; + +/** + * \brief GFH File Info body (48 bytes, follows common header when type=FILE_INFO). + * + * Contains the primary metadata for a MediaTek firmware image: load address, + * entry point offset, header area size, and signature information. + * + * Reference: https://github.com/nccgroup/mtk_bp/blob/main/mtk_structs/mtk_img.ksy + */ +typedef struct mtk_gfh_file_info { + char name[MTK_GFH_FILE_INFO_NAME_SIZE]; ///< Null-terminated identifier + ut32 unused; + ut16 file_type; + ut8 flash_type; + ut8 sig_type; + ut32 load_addr; ///< Base load address in memory + ut32 total_size; ///< Total image size + ut32 max_size; + ut32 hdr_size; ///< Total header area size; code starts at this file offset + ut32 sig_size; ///< Signature size in bytes + ut32 jump_offset; ///< File offset of entry point (from start of image) + ut32 processed; +} MtkGfhFileInfo; + +/** + * \brief A parsed additional GFH header (stored generically). + */ +typedef struct mtk_gfh_header { + MtkGfhCommonHdr common; ///< Common header fields + ut64 file_offset; ///< File offset where this header starts +} MtkGfhHeader; + +/* Maximum offset to scan for GFH magic when it's not at file start */ +#define MTK_GFH_SCAN_LIMIT 0x1000 + +/** + * \brief Top-level parsed object stored as bin_obj. + */ +typedef struct mtk_obj { + MtkGfhCommonHdr first_common; ///< Common header of the first (file_info) block + MtkGfhFileInfo file_info; ///< Parsed file_info body + RzVector /**/ *extra_headers; ///< Additional GFH headers after file_info + ut64 gfh_offset; ///< File offset where GFH starts (0 if at beginning) + ut32 code_offset; ///< File offset where code starts (= gfh_offset + file_info.hdr_size) + ut32 code_size; ///< Size of code section + ut32 entry_vaddr; ///< = MTK_MODEM_BADDR + (jump_offset - code_offset_relative) +} MtkObj; + +RZ_IPI RZ_OWN MtkObj *mtk_obj_new(RZ_BORROW RZ_NONNULL RzBuffer *b); +RZ_IPI void mtk_obj_free(RZ_OWN RZ_NULLABLE MtkObj *mtk); +RZ_IPI RZ_BORROW const char *mtk_gfh_type_str(MtkGfhType type); + +RZ_IPI bool mtk_check_buffer(RZ_BORROW RZ_NONNULL RzBuffer *b); +RZ_IPI bool mtk_load_buffer(RZ_BORROW RZ_NONNULL RzBinFile *bf, RZ_BORROW RZ_NONNULL RzBinObject *obj, RZ_BORROW RZ_NONNULL RzBuffer *b, RZ_BORROW RZ_NULLABLE Sdb *sdb); +RZ_IPI void mtk_destroy(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzBinInfo *mtk_info(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI ut64 mtk_baddr(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *mtk_entries(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzPVector /**/ *mtk_sections(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI bool mtk_append_maps(RZ_BORROW RZ_NONNULL MtkObj *mtk, ut64 paddr, RZ_BORROW RZ_NULLABLE const char *name, RZ_BORROW RZ_NONNULL RzPVector /**/ *ret); +RZ_IPI RZ_OWN RzPVector /**/ *mtk_maps(RZ_BORROW RZ_NONNULL RzBinFile *bf); +RZ_IPI RZ_OWN RzStructuredData *mtk_structure(RZ_BORROW RZ_NONNULL RzBinFile *bf); + +#endif diff --git a/librz/bin/meson.build b/librz/bin/meson.build index da40667708..c2d859f11d 100644 --- a/librz/bin/meson.build +++ b/librz/bin/meson.build @@ -24,8 +24,10 @@ bin_plugins_list = [ 'mach0', 'mach064', 'mbn', + 'md1img', 'mdmp', 'mdt', + 'mtk', 'menuet', 'mz', 'ne', @@ -130,7 +132,9 @@ rz_bin_sources = [ 'p/bin_mach064.c', 'p/bin_mbn.c', 'p/bin_mdmp.c', + 'p/bin_md1img.c', 'p/bin_mdt.c', + 'p/bin_mtk.c', 'p/bin_menuet.c', 'p/bin_mz.c', 'p/bin_ne.c', @@ -242,6 +246,8 @@ rz_bin_sources = [ 'format/mdmp/mdmp_pe.c', 'format/mdmp/mdmp_pe64.c', 'format/mdt/mdt.c', + 'format/mtk/md1img.c', + 'format/mtk/mtk.c', 'format/le/le.c', 'format/luac/luac_common.c', 'format/luac/luac_bin.c', diff --git a/librz/bin/p/bin_md1img.c b/librz/bin/p/bin_md1img.c new file mode 100644 index 0000000000..4373cf6cdf --- /dev/null +++ b/librz/bin/p/bin_md1img.c @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 godcodehunter +// SPDX-License-Identifier: LGPL-3.0-only + +#include "../format/mtk/md1img.h" + +RzBinPlugin rz_bin_plugin_md1img = { + .name = "md1img", + .desc = "MediaTek md1img firmware container", + .license = "LGPL3", + .author = "godcodehunter", + .check_buffer = &md1img_check_buffer, + .load_buffer = &md1img_load_buffer, + .destroy = &md1img_destroy, + .baddr = &md1img_baddr, + .entries = &md1img_entries, + .virtual_files = &md1img_virtual_files, + .maps = &md1img_maps, + .sections = &md1img_sections, + .symbols = &md1img_symbols, + .strings = &md1img_strings, + .info = &md1img_info, + .bin_structure = &md1img_structure, +}; + +#ifndef RZ_PLUGIN_INCORE +RZ_API RzLibStruct rizin_plugin = { + .type = RZ_LIB_TYPE_BIN, + .data = &rz_bin_plugin_md1img, + .version = RZ_VERSION +}; +#endif diff --git a/librz/bin/p/bin_mtk.c b/librz/bin/p/bin_mtk.c new file mode 100644 index 0000000000..a312c89128 --- /dev/null +++ b/librz/bin/p/bin_mtk.c @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 godcodehunter +// SPDX-License-Identifier: LGPL-3.0-only + +#include "../format/mtk/mtk.h" + +RzBinPlugin rz_bin_plugin_mtk = { + .name = "mtk", + .desc = "MediaTek GFH firmware image (md1rom)", + .license = "LGPL3", + .author = "godcodehunter", + .check_buffer = &mtk_check_buffer, + .load_buffer = &mtk_load_buffer, + .destroy = &mtk_destroy, + .baddr = &mtk_baddr, + .entries = &mtk_entries, + .sections = &mtk_sections, + .maps = &mtk_maps, + .info = &mtk_info, + .bin_structure = &mtk_structure, +}; + +#ifndef RZ_PLUGIN_INCORE +RZ_API RzLibStruct rizin_plugin = { + .type = RZ_LIB_TYPE_BIN, + .data = &rz_bin_plugin_mtk, + .version = RZ_VERSION +}; +#endif diff --git a/librz/include/rz_util/rz_buf.h b/librz/include/rz_util/rz_buf.h index 4963304d00..bd514f5369 100644 --- a/librz/include/rz_util/rz_buf.h +++ b/librz/include/rz_util/rz_buf.h @@ -402,6 +402,7 @@ RZ_API bool rz_inflatew_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, RZ_API bool rz_inflate_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size, ut8 *src_consumed); RZ_API bool rz_lzma_dec_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size, ut8 *src_consumed); RZ_API bool rz_lzma_enc_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size, ut8 *src_consumed); +RZ_API bool rz_lzma_alone_dec_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size); #ifdef __cplusplus } diff --git a/librz/util/compression.c b/librz/util/compression.c index 39abd696c1..87e5daeef6 100644 --- a/librz/util/compression.c +++ b/librz/util/compression.c @@ -480,3 +480,91 @@ RZ_API bool rz_lzma_dec_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, RZ_API bool rz_lzma_enc_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size, ut8 *src_consumed) { return lzma_action_buf(src, dst, block_size, src_consumed, true); } + +#if HAVE_LZMA +static bool lzma_alone_action_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size) { + bool res = true; + lzma_stream strm = LZMA_STREAM_INIT; + const ut64 memusage_limit = 0x8000000; // 128 MB + lzma_ret ret = lzma_alone_decoder(&strm, memusage_limit); + if (ret != LZMA_OK) { + return false; + } + + lzma_action action = LZMA_RUN; + + ut8 *inbuf = RZ_NEWS(ut8, block_size); + ut8 *outbuf = RZ_NEWS(ut8, block_size); + if (!inbuf || !outbuf) { + free(inbuf); + free(outbuf); + lzma_end(&strm); + return false; + } + ut64 src_cursor = 0; + + strm.next_in = NULL; + strm.avail_in = 0; + strm.next_out = outbuf; + strm.avail_out = block_size; + + while (true) { + if (strm.avail_in == 0) { + strm.next_in = inbuf; + st64 src_readlen = rz_buf_read_at(src, src_cursor, inbuf, block_size); + if (src_readlen < 0) { + res = false; + goto exit; + } + if (src_readlen == 0) { + action = LZMA_FINISH; + } + strm.avail_in = src_readlen; + src_cursor += src_readlen; + } + ret = lzma_code(&strm, action); + if (strm.avail_out == 0 || ret == LZMA_STREAM_END) { + size_t write_size = block_size - strm.avail_out; + if (rz_buf_write(dst, outbuf, write_size) != write_size) { + res = false; + goto exit; + } + strm.next_out = outbuf; + strm.avail_out = block_size; + } + if (ret == LZMA_STREAM_END) { + break; + } + if (ret != LZMA_OK) { + res = false; + goto exit; + } + } + +exit: + free(inbuf); + free(outbuf); + lzma_end(&strm); + return res; +} +#else +static bool lzma_alone_action_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size) { + return false; +} +#endif + +/** + * \brief Decompress the \p src buffer with LZMA alone (raw LZMA1) algorithm and put the decompressed data in \p dst + * + * Unlike rz_lzma_dec_buf() which handles .xz streams, this function handles + * the legacy LZMA alone format (also known as LZMA1 or .lzma), identifiable + * by the 0x5d first byte followed by a 13-byte header. + * + * \param src Where to read the compressed data from + * \param dst Where to write the decompressed data to + * \param block_size Decompression block size for I/O operations + * \return true if decompression was successful, false otherwise + */ +RZ_API bool rz_lzma_alone_dec_buf(RZ_NONNULL RzBuffer *src, RZ_NONNULL RzBuffer *dst, ut64 block_size) { + return lzma_alone_action_buf(src, dst, block_size); +} diff --git a/test/db/cmd/cmd_list b/test/db/cmd/cmd_list index 0a4830e78d..1c46312314 100644 --- a/test/db/cmd/cmd_list +++ b/test/db/cmd/cmd_list @@ -206,9 +206,11 @@ luac LGPL3 Heersin Lua compiled binary mach0 LGPL3 pancake Mach-O (Mach Object) mach064 LGPL3 nibble Mach-O 64-bit mbn LGPL3 pancake MBN/SBL bootloader binary +md1img LGPL3 godcodehunter MediaTek md1img firmware container mdmp LGPL3 Davis Windows MiniDump mdt LGPL3 Rot127 Qualcomm Peripheral Image Loader (32bit only) menuet LGPL3 pancake Menuet/KolibriOS binary +mtk LGPL3 godcodehunter MediaTek GFH firmware image (md1rom) mz MIT nodepad MZ (DOS MZ Executable) ne LGPL3 GustavoLCR NE (New Executable) nes MIT maijin Nintendo NES @@ -243,7 +245,7 @@ zimg LGPL3 deroad zImage format binary .cart LGPL3 CaRT (Compressed and RC4 Transport) extractor .fatmach0 LGPL3 Fat Mach-O binary extractor .sep64 LGPL3 64-bit SEP binary extractor -[{"name":"any","description":"Dummy format binary","license":"LGPL3","author":"nibble"},{"name":"art","description":"Android Runtime","license":"LGPL3","author":"pancake"},{"name":"avr","description":"ATmel AVR usermode","license":"LGPL3","author":"deroad"},{"name":"bf","description":"Brainfuck","license":"LGPL3","author":"pancake"},{"name":"bflt","description":"bFLT uClinux binary","license":"LGPL3","author":"Oscar Salvador"},{"name":"bios","description":"BIOS binary","license":"LGPL","author":"pancake"},{"name":"bootimg","description":"Android Boot Image","license":"LGPL3","author":"pancake"},{"name":"c166","description":"Siemens/Infineon C166 family microcontroller binary","license":"LGPL3","author":"SSharshunov"},{"name":"cgc","description":"CGC (Cyber Grand Challenge)","license":"LGPL3","author":"ret2libc"},{"name":"coff","description":"COFF (Common Object File Format)","license":"LGPL3","author":"Fedor Sakharov"},{"name":"dex","description":"DEX (Dalvik Executable)","license":"LGPL3","author":"deroad"},{"name":"dmp64","description":"Windows x64 Crash Dump","license":"LGPL3","author":"abcSup"},{"name":"dol","description":"Nintendo Dolphin binary","license":"BSD","author":"pancake"},{"name":"dyldcache","description":"Apple DYLD Cache","license":"LGPL3","author":"pancake"},{"name":"ecoff","description":"ECOFF (Extended Common Object File Format)","license":"LGPL3","author":"deroad"},{"name":"elf","description":"ELF (Executable and Linkable Format)","license":"LGPL3","author":"nibble"},{"name":"elf64","description":"ELF64 (64-bit Executable and Linkable Format)","license":"LGPL3","author":"nibble"},{"name":"gns1","description":"Apple C4000 baseband firmware (GNS1.bin)","license":"LGPL3","author":"Zapper9982"},{"name":"hunk","description":"Amiga Hunk file format","license":"LGPL3","author":"StefBisti"},{"name":"java","description":"Java binary","license":"LGPL3","author":"deroad"},{"name":"kernelcache","description":"Apple Kernelcache","license":"LGPL3","author":"mrmacete"},{"name":"le","description":"LE/LX","license":"LGPL3","author":"GustavoLCR"},{"name":"luac","description":"Lua compiled binary","license":"LGPL3","author":"Heersin"},{"name":"mach0","description":"Mach-O (Mach Object)","license":"LGPL3","author":"pancake"},{"name":"mach064","description":"Mach-O 64-bit","license":"LGPL3","author":"nibble"},{"name":"mbn","description":"MBN/SBL bootloader binary","license":"LGPL3","author":"pancake"},{"name":"mdmp","description":"Windows MiniDump","license":"LGPL3","author":"Davis"},{"name":"mdt","description":"Qualcomm Peripheral Image Loader (32bit only)","license":"LGPL3","author":"Rot127"},{"name":"menuet","description":"Menuet/KolibriOS binary","license":"LGPL3","author":"pancake"},{"name":"mz","description":"MZ (DOS MZ Executable)","license":"MIT","author":"nodepad"},{"name":"ne","description":"NE (New Executable)","license":"LGPL3","author":"GustavoLCR"},{"name":"nes","description":"Nintendo NES","license":"MIT","author":"maijin"},{"name":"nin3ds","description":"Nintendo 3DS Firmware","license":"LGPL3","author":"a0rtega"},{"name":"ninds","description":"Nintendo DS binary","license":"LGPL3","author":"a0rtega"},{"name":"ningb","description":"Nintendo Game Boy","license":"LGPL3","author":"condret"},{"name":"ningba","description":"Nintendo Game Boy Advance","license":"LGPL3","author":"condret"},{"name":"nro","description":"Nintendo Switch NRO","license":"MIT","author":"pancake"},{"name":"nso","description":"Nintendo Switch NSO","license":"MIT","author":"rkx1209"},{"name":"omf","description":"OMF (Object Module Format)","license":"LGPL3","author":"ampotos"},{"name":"omf166","description":"OMF166 (Object Module Format by Siemens)","license":"LGPL3","author":"SSharshunov"},{"name":"p9","description":"Plan9 binary","license":"LGPL3","author":"pancake"},{"name":"pe","description":"PE (Portable Executable)","license":"LGPL3","author":"nibble"},{"name":"pe64","description":"PE64 (PE32+) binary","license":"LGPL3","author":"nibble"},{"name":"pebble","description":"Pebble Watch App","license":"LGPL","author":"pancake"},{"name":"pef","description":"Preferred Executable Format","license":"LGPL3","author":"farhan-25"},{"name":"prg","description":"C64 PRG (Commodore 64)","license":"LGPL3","author":"thestr4ng3r"},{"name":"psxexe","description":"Sony PlayStation 1 executable","license":"LGPL3","author":"Dax89"},{"name":"pyc","description":"Python byte-compiled","license":"LGPL3","author":"c0riolis"},{"name":"qnx","description":"QNX executable","license":"LGPL3","author":"deepakchethan"},{"name":"sfc","description":"Super NES / Super Famicom ROM","license":"LGPL3","author":"usrshare"},{"name":"smd","description":"SEGA Genesis / MegaDrive ROM","license":"LGPL3","author":"pancake"},{"name":"sms","description":"SEGA MasterSystem / GameGear ROM","license":"LGPL3","author":"shengdi"},{"name":"spc700","description":"SNES SPC700 sound data","license":"LGPL3","author":"maijin"},{"name":"symbols","description":"Apple Symbols file","license":"MIT","author":"pancake"},{"name":"te","description":"TE (Terse Executable)","license":"LGPL3","author":"xvilka"},{"name":"vsf","description":"VICE snapshot file","license":"LGPL3","author":"riq"},{"name":"wasm","description":"WebAssembly","license":"MIT","author":"pancake"},{"name":"xbe","description":"Microsoft Xbox XBE (Xbox Executable)","license":"LGPL3","author":"LemonBoy"},{"name":"z64","description":"Nintendo 64 Big-Endian binary","license":"LGPL3","author":"lowlyw"},{"name":"zimg","description":"zImage format binary","license":"LGPL3","author":"deroad"},{"name":"xtr.cart","description":"CaRT (Compressed and RC4 Transport) extractor","license":"LGPL3"},{"name":"xtr.fatmach0","description":"Fat Mach-O binary extractor","license":"LGPL3"},{"name":"xtr.sep64","description":"64-bit SEP binary extractor","license":"LGPL3"}] +[{"name":"any","description":"Dummy format binary","license":"LGPL3","author":"nibble"},{"name":"art","description":"Android Runtime","license":"LGPL3","author":"pancake"},{"name":"avr","description":"ATmel AVR usermode","license":"LGPL3","author":"deroad"},{"name":"bf","description":"Brainfuck","license":"LGPL3","author":"pancake"},{"name":"bflt","description":"bFLT uClinux binary","license":"LGPL3","author":"Oscar Salvador"},{"name":"bios","description":"BIOS binary","license":"LGPL","author":"pancake"},{"name":"bootimg","description":"Android Boot Image","license":"LGPL3","author":"pancake"},{"name":"c166","description":"Siemens/Infineon C166 family microcontroller binary","license":"LGPL3","author":"SSharshunov"},{"name":"cgc","description":"CGC (Cyber Grand Challenge)","license":"LGPL3","author":"ret2libc"},{"name":"coff","description":"COFF (Common Object File Format)","license":"LGPL3","author":"Fedor Sakharov"},{"name":"dex","description":"DEX (Dalvik Executable)","license":"LGPL3","author":"deroad"},{"name":"dmp64","description":"Windows x64 Crash Dump","license":"LGPL3","author":"abcSup"},{"name":"dol","description":"Nintendo Dolphin binary","license":"BSD","author":"pancake"},{"name":"dyldcache","description":"Apple DYLD Cache","license":"LGPL3","author":"pancake"},{"name":"ecoff","description":"ECOFF (Extended Common Object File Format)","license":"LGPL3","author":"deroad"},{"name":"elf","description":"ELF (Executable and Linkable Format)","license":"LGPL3","author":"nibble"},{"name":"elf64","description":"ELF64 (64-bit Executable and Linkable Format)","license":"LGPL3","author":"nibble"},{"name":"gns1","description":"Apple C4000 baseband firmware (GNS1.bin)","license":"LGPL3","author":"Zapper9982"},{"name":"hunk","description":"Amiga Hunk file format","license":"LGPL3","author":"StefBisti"},{"name":"java","description":"Java binary","license":"LGPL3","author":"deroad"},{"name":"kernelcache","description":"Apple Kernelcache","license":"LGPL3","author":"mrmacete"},{"name":"le","description":"LE/LX","license":"LGPL3","author":"GustavoLCR"},{"name":"luac","description":"Lua compiled binary","license":"LGPL3","author":"Heersin"},{"name":"mach0","description":"Mach-O (Mach Object)","license":"LGPL3","author":"pancake"},{"name":"mach064","description":"Mach-O 64-bit","license":"LGPL3","author":"nibble"},{"name":"mbn","description":"MBN/SBL bootloader binary","license":"LGPL3","author":"pancake"},{"name":"md1img","description":"MediaTek md1img firmware container","license":"LGPL3","author":"godcodehunter"},{"name":"mdmp","description":"Windows MiniDump","license":"LGPL3","author":"Davis"},{"name":"mdt","description":"Qualcomm Peripheral Image Loader (32bit only)","license":"LGPL3","author":"Rot127"},{"name":"menuet","description":"Menuet/KolibriOS binary","license":"LGPL3","author":"pancake"},{"name":"mtk","description":"MediaTek GFH firmware image (md1rom)","license":"LGPL3","author":"godcodehunter"},{"name":"mz","description":"MZ (DOS MZ Executable)","license":"MIT","author":"nodepad"},{"name":"ne","description":"NE (New Executable)","license":"LGPL3","author":"GustavoLCR"},{"name":"nes","description":"Nintendo NES","license":"MIT","author":"maijin"},{"name":"nin3ds","description":"Nintendo 3DS Firmware","license":"LGPL3","author":"a0rtega"},{"name":"ninds","description":"Nintendo DS binary","license":"LGPL3","author":"a0rtega"},{"name":"ningb","description":"Nintendo Game Boy","license":"LGPL3","author":"condret"},{"name":"ningba","description":"Nintendo Game Boy Advance","license":"LGPL3","author":"condret"},{"name":"nro","description":"Nintendo Switch NRO","license":"MIT","author":"pancake"},{"name":"nso","description":"Nintendo Switch NSO","license":"MIT","author":"rkx1209"},{"name":"omf","description":"OMF (Object Module Format)","license":"LGPL3","author":"ampotos"},{"name":"omf166","description":"OMF166 (Object Module Format by Siemens)","license":"LGPL3","author":"SSharshunov"},{"name":"p9","description":"Plan9 binary","license":"LGPL3","author":"pancake"},{"name":"pe","description":"PE (Portable Executable)","license":"LGPL3","author":"nibble"},{"name":"pe64","description":"PE64 (PE32+) binary","license":"LGPL3","author":"nibble"},{"name":"pebble","description":"Pebble Watch App","license":"LGPL","author":"pancake"},{"name":"pef","description":"Preferred Executable Format","license":"LGPL3","author":"farhan-25"},{"name":"prg","description":"C64 PRG (Commodore 64)","license":"LGPL3","author":"thestr4ng3r"},{"name":"psxexe","description":"Sony PlayStation 1 executable","license":"LGPL3","author":"Dax89"},{"name":"pyc","description":"Python byte-compiled","license":"LGPL3","author":"c0riolis"},{"name":"qnx","description":"QNX executable","license":"LGPL3","author":"deepakchethan"},{"name":"sfc","description":"Super NES / Super Famicom ROM","license":"LGPL3","author":"usrshare"},{"name":"smd","description":"SEGA Genesis / MegaDrive ROM","license":"LGPL3","author":"pancake"},{"name":"sms","description":"SEGA MasterSystem / GameGear ROM","license":"LGPL3","author":"shengdi"},{"name":"spc700","description":"SNES SPC700 sound data","license":"LGPL3","author":"maijin"},{"name":"symbols","description":"Apple Symbols file","license":"MIT","author":"pancake"},{"name":"te","description":"TE (Terse Executable)","license":"LGPL3","author":"xvilka"},{"name":"vsf","description":"VICE snapshot file","license":"LGPL3","author":"riq"},{"name":"wasm","description":"WebAssembly","license":"MIT","author":"pancake"},{"name":"xbe","description":"Microsoft Xbox XBE (Xbox Executable)","license":"LGPL3","author":"LemonBoy"},{"name":"z64","description":"Nintendo 64 Big-Endian binary","license":"LGPL3","author":"lowlyw"},{"name":"zimg","description":"zImage format binary","license":"LGPL3","author":"deroad"},{"name":"xtr.cart","description":"CaRT (Compressed and RC4 Transport) extractor","license":"LGPL3"},{"name":"xtr.fatmach0","description":"Fat Mach-O binary extractor","license":"LGPL3"},{"name":"xtr.sep64","description":"64-bit SEP binary extractor","license":"LGPL3"}] name license author description version ------------------------------------------------------------------------------------------------ any LGPL3 nibble Dummy format binary @@ -272,9 +274,11 @@ luac LGPL3 Heersin Lua compiled binary mach0 LGPL3 pancake Mach-O (Mach Object) mach064 LGPL3 nibble Mach-O 64-bit mbn LGPL3 pancake MBN/SBL bootloader binary +md1img LGPL3 godcodehunter MediaTek md1img firmware container mdmp LGPL3 Davis Windows MiniDump mdt LGPL3 Rot127 Qualcomm Peripheral Image Loader (32bit only) menuet LGPL3 pancake Menuet/KolibriOS binary +mtk LGPL3 godcodehunter MediaTek GFH firmware image (md1rom) mz MIT nodepad MZ (DOS MZ Executable) ne LGPL3 GustavoLCR NE (New Executable) nes MIT maijin Nintendo NES @@ -335,9 +339,11 @@ luac mach0 mach064 mbn +md1img mdmp mdt menuet +mtk mz ne nes diff --git a/test/db/formats/mtk b/test/db/formats/mtk new file mode 100644 index 0000000000..303b89f86d --- /dev/null +++ b/test/db/formats/mtk @@ -0,0 +1,227 @@ +NAME=md1rom synthetic GFH firmware image +FILE=bins/mtk/md1rom +CMDS=<