Перейти к точке входа ELF из загрузчика - PullRequest
3 голосов
/ 08 апреля 2019

Благодаря помощи в этом вопросе, загрузчик теперь может отобразить статически скомпилированный мир приветствия в память и перейти куда-нибудь в эту область памяти.Проблема, с которой я сейчас сталкиваюсь, заключается в том, что я, похоже, не перехожу по правильному адресу или вызываю функцию неправильным образом (или неправильной функцией?).

Ниже приведен код для попытки;Я не могу найти в glibc, где загрузчик вызывает точку входа в программу, чтобы убедиться, что я поступаю правильно.Я попытался:

  • Вызов _start (void)
  • Вызов _start (int, char **, char **)
  • Вызов __libc_start_main с последними параметрами, установленными вNULL (найдено здесь )
  • Вызов напрямую по основному номеру
  • Вызов некоторых адресов (найден с помощью gdb в rdi: 400B4D, r8: 4018E0, rcx: 401840)

Точка входа - 0x400a30, некоторые инструкции приведены ниже __libc_start_main.С некоторыми из них это SIGSEGV, SIGABRT или распечатки:

hello, world! haswell xeon_phi ../csu/libc-start.c FATAL: kernel too old
   __ehdr_start.e_phentsize == sizeof *GL(dl_phdr) unexpected reloc type in static binary  FATAL: cannot determine kernel version
 __libc_start_main /dev/full /dev/null   cannot set %fs base address for thread-local storage :  %s%s%s:%u: %s%sAssertion `%s' failed.
%n        Unexpected error.

и несколько сотен ненужных строк.

int main(int argc, char* argv[argc+1]) {
    FILE *fp = fopen(argv[1], "r");
    if (!fp) {
        fprintf(stderr, "cannot open file %s", argv[1]);
        return 1;
    }

    fseek(fp, 0L, SEEK_END);
    size_t sz = ftell(fp) + 1;
    rewind(fp);

    char *region = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
    if (region == MAP_FAILED) {
        fprintf(stderr, "could not mmap");
        return 1;
    }

    Elf64_Ehdr elf64Ehdr;
    memmove(&elf64Ehdr, region, sizeof(elf64Ehdr));

    size_t offset;
    Elf64_Phdr elf64Phdr;
    for (uint16_t i = 0; i != elf64Ehdr.e_phnum; ++i) {
        offset = elf64Ehdr.e_phoff + i * elf64Ehdr.e_phentsize;
        memmove(&elf64Phdr, region + offset, sizeof(elf64Phdr));
        switch (elf64Phdr.p_type) {
            case PT_NULL:
                break;
            case PT_LOAD:
                if (load(&elf64Phdr, region + elf64Phdr.p_offset)) {
                    exit(EXIT_FAILURE);
                }
                break;
            default:
                break;
        }
    }
    printf("jumping to: 0x%x\n", elf64Ehdr.e_entry);

    char *argv1[] = {"", NULL};

    int ret = ((int (*)(int, char **, char **)) elf64Ehdr.e_entry)(1, argv1, argv1);
    return ret;
}

int load(const Elf64_Phdr *phdr, const void *elf_bytes_for_phdr) {
    const size_t pagesize = getpagesize();
    const size_t unaligned_bytes = phdr->p_vaddr % pagesize;

    void *base_addr = phdr->p_vaddr - unaligned_bytes;
    size_t total_bytes = phdr->p_memsz + unaligned_bytes;

    void *region = mmap(base_addr, total_bytes,
            phdr->p_flags | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
    if (region != MAP_FAILED) {
        memset(region, 0, unaligned_bytes);
        return memcpy(region + unaligned_bytes, elf_bytes_for_phdr, phdr->p_filesz) != region + unaligned_bytes && 
        ! mprotect(region, total_bytes, phdr->p_flags);
    }
    return 1;
}

1 Ответ

3 голосов
/ 08 апреля 2019

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

Ваша проблема не относится ни к одному из вышеперечисленных (хотя "неправильный путь" не так уж и далек).

Для статически связанного исполняемого файла Elf64_Ehdr.e_entry является правильным адресом для вызова (он указывает на _start), а _start не принимает аргументов.

Проблема в том, что это работа от _start до

  1. инициализировать libc и
  2. для поиска правильных значений argc, argv и envp и, наконец,
  3. позвонить main(argc, argv, envp).

Тогда возникает вопрос: как _start может выполнить шаг 2?

Ответ: есть протокол, который ядро ​​Linux реализует , а _start использует для выполнения шага 2.

В частности, ядро ​​копирует действительные (строковые) значения argv[0], argv[1], ... envp[0], envp[1] и т. Д. В стек, а затем указатели на эти строки. Существует также нечто, называемое вспомогательным вектором.

_start ожидает, что найдет всю эту информацию в стеке, и будет плохо себя вести, когда не найдет ее. Я считаю, что это коренная причина вашей нынешней проблемы.

Вот статья , которая объясняет ожидаемую установку со ссылками на исходный код ядра Linux. Еще одна статья .

...