Проблемы с загрузчиком ELF x86 в Linux - PullRequest
2 голосов
/ 10 июня 2019

Я пытаюсь написать исполняемый загрузчик ELF для Linux x86-64, аналогично this , который был реализован на ARM. У продвинутого класса ОС Криса Россбаха есть лаборатория, которая делает в основном то, что я хочу.Моя цель состоит в том, чтобы загрузить простой (статически связанный) двоичный файл типа "привет мир" в память моего процесса и запустить его без execve ing.Я успешно mmap запустил файл ELF, настроил стек и перешел к точке входа ELF (_start).

// put ELF file into memory. This is just one line of a complex
// for() loop that loads the binary from a file.
mmap((void*)program_header.p_vaddr, program_header.p_memsz, map, MAP_PRIVATE|MAP_FIXED, elffd, program_header.p_offset);


newstack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); // Map a page for the stack

if((long)newstack < 0) {
  fprintf(stderr, "ERROR: mmap returned error when allocating stack, %s\n", strerror(errno));
  exit(1);
}

topstack = (unsigned long*)((unsigned char*)newstack+4096); // Top of new stack

*((unsigned long*)topstack-1) = 0; // Set up the stack
*((unsigned long*)topstack-2) = 0; // with argc, argv[], etc.
*((unsigned long*)topstack-3) = 0;
*((unsigned long*)topstack-4) = argv[1];
*((unsigned long*)topstack-5) = 1;

asm("mov %0,%%rsp\n"     // Install new stack pointer
    "xor %%rax, %%rax\n" // Zero registers
    "xor %%rbx, %%rbx\n"
    "xor %%rcx, %%rcx\n"
    "xor %%rdx, %%rdx\n"
    "xor %%rsi, %%rsi\n"
    "xor %%rdi, %%rdi\n"
    "xor %%r8, %%r8\n"
    "xor %%r9, %%r9\n"
    "xor %%r10, %%r10\n"
    "xor %%r11, %%r11\n"
    "xor %%r12, %%r12\n"
    "xor %%r13, %%r13\n"
    "xor %%r14, %%r14\n"
    :   
    : "r"(topstack-5)
    :"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14");
asm("push %%rax\n"
    "pop %%rax\n"
    :   
    :   
    : "rax");

asm("mov %0,%%rax\n" // Jump to the entry point of the loaded ELF file
    "jmp *%%rax\n"
    :   
    : "r"(jump_target)
    : );

Затем я перешагиваю этот код в gdb.Я вставил несколько первых инструкций кода запуска ниже.Все отлично работает до первой инструкции push (помеченной).push вызывает segfault.

0x60026000      xor    %ebp,%ebp
0x60026002      mov    %rdx,%r9
0x60026005      pop    %rsi
0x60026006      mov    %rsp,%rdx
0x60026009      and    $0xfffffffffffffff0,%rsp
0x6002600d *    push   %rax
0x6002600e      push   %rsp
0x6002600f      mov    $0x605f4990,%r8

Я пытался:

  1. Использование стека из исходного процесса.
  2. mmap с новымstack (как в вышеприведенном коде): (1) и (2) оба вызывают segfaults.
  3. push ing и pop вход в / из стека перед jmp входом в загруженный файл ELF,Это не вызывает segfault.
  4. Изменение флагов защиты для стека во втором mmap на PROT_READ | PROT_WRITE | PROT_EXEC.Это не имеет значения.

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

1 Ответ

0 голосов
/ 13 июня 2019

Оказалось, что когда я проходил через загруженный код в gdb, отладчик последовательно выдавал первую команду push, когда я набирал nexti, и вместо этого продолжал выполнение. На самом деле это была не инструкция push, которая вызывала ошибку, а гораздо более поздняя инструкция в стартовом коде библиотеки C. Проблема была вызвана неудачным вызовом mmap в начальной двоичной загрузке, который я не проверял.

Относительно gdb, произвольно решив продолжить выполнение вместо шага: это можно исправить, загрузив символы из целевого исполняемого файла после перехода к вновь загруженному исполняемому файлу.

...