Разбор универсальных / толстых бинарных файлов

Я работаю над проектом по реализации основного nm используя отображение памяти mmap, Мне удалось проанализировать 64-разрядные двоичные файлы с помощью кода:

void        handle_64(char *ptr)
{
    int                     ncmds;
    struct mach_header_64   *header;
    struct load_command     *lc;
    struct symtab_command   *sym;
    int                     i;

    i = 0;
    header = (struct mach_header_64 *)ptr;
    ncmds = header->ncmds;
    lc = (void *)ptr + sizeof(*header);
    while (i < ncmds)
    {
        if (lc->cmd == LC_SYMTAB)
        {
            sym = (struct symtab_command *)lc;
            build_list (sym->nsyms, sym->symoff, sym->stroff, ptr);
            break;
         }
         lc = (void *) lc + lc->cmdsize;
         i++;
    }
}

Согласно этой ссылке единственное различие между mach-o и толстым бинарным fat_header структура над ним, но просто пропуская с

lc = (void *)ptr + sizeof(struct fat_header) + sizeof(struct mach_header_64);

не доставляет меня в область load_command (segfault). Как получить доступ к командам загрузки толстого / универсального двоичного файла.

Я работаю на 64-битном Mac под управлением MacOS High Sierra. Спасибо.

1 ответ

Решение

У вас есть несколько проблем:

  • То, что этот блог, на который вы ссылаетесь, называется "толстый заголовок", это больше, чем просто struct fat_header,
  • Нигде не гарантируется, что заголовок Mach-O будет следовать сразу после толстого заголовка, сразу после него (обычно сегменты Mach-O хотят быть выровнены по целым страницам, поэтому размещение их сразу после толстого заголовка может даже не работать).
  • Нигде не гарантируется, что 64-битный срез будет первым в двоичном файле или что он будет даже один.

Учитывая все это, вам нужно анализировать толстый заголовок (а не просто игнорировать его), если вы хотите надеяться на получение полезных результатов.

Сейчас, fat_header определяется следующим образом:

struct fat_header {
    uint32_t    magic;      /* FAT_MAGIC or FAT_MAGIC_64 */
    uint32_t    nfat_arch;  /* number of structs that follow */
};

Во-первых, волшебная ценность, которую я обычно вижу для толстых двоичных файлов, FAT_CIGAM скорее, чем FAT_MAGIC, несмотря на комментарий, в котором говорится иначе (будьте осторожны - это означает, что целые числа в толстом заголовке имеют порядковый, а не порядковый номер!). Но во-вторых, указывается, что за этим заголовком следуют определенные структуры, а именно:

struct fat_arch {
    cpu_type_t  cputype;    /* cpu specifier (int) */
    cpu_subtype_t   cpusubtype; /* machine specifier (int) */
    uint32_t    offset;     /* file offset to this object file */
    uint32_t    size;       /* size of this object file */
    uint32_t    align;      /* alignment as a power of 2 */
};

Это работает так же, как "тонкий" заголовок Mach-O со своими командами загрузки. fat_arch.offset смещение от самого начала файла. После этого довольно просто напечатать все кусочки жирного Mach-O:

#include <stdio.h>
#include <mach-o/fat.h>

#define SWAP32(x) ((((x) & 0xff000000) >> 24) | (((x) & 0xff0000) >> 8) | (((x) & 0xff00) << 8) | (((x) & 0xff) << 24))

void print_fat_header(void *buf)
{
    struct fat_header *hdr = buf;
    if(hdr->magic != FAT_CIGAM)
    {
        fprintf(stderr, "bad magic: %08x\n", hdr->magic);
        return;
    }
    struct fat_arch *archs = (struct fat_arch*)(hdr + 1);
    uint32_t num = SWAP32(hdr->nfat_arch);
    for(size_t i = 0; i < num; ++i)
    {
        const char *name = "unknown";
        switch(SWAP32(archs[i].cputype))
        {
            case CPU_TYPE_I386:     name = "i386";      break;
            case CPU_TYPE_X86_64:   name = "x86_64";    break;
            case CPU_TYPE_ARM:      name = "arm";       break;
            case CPU_TYPE_ARM64:    name = "arm64";     break;
        }
        uint32_t off = SWAP32(archs[i].offset);
        uint32_t magic = *(uint32_t*)((uintptr_t)buf + off);
        printf("%08x-%08x: %-8s (magic %8x)\n", off, off + SWAP32(archs[i].size), name, magic);
    }
}

Обратите внимание, что вышеуказанная функция является неполной, так как она не знает длины buf и, следовательно, не может и не проверяет любую доступную память против него. В серьезной реализации вы должны быть уверены, что никогда не читаете за пределами заданного вами буфера. Тот факт, что ваш код сегментирован также намекает на то, что он не выполняет достаточную очистку данных.

Другие вопросы по тегам