Можете ли вы ввести x64 32-битный "длинный режим совместимости" вне режима ядра?

Это может быть точной копией. Можно ли выполнить 32-битный код в 64-битном процессе путем переключения режимов?, но этот вопрос из года назад и имеет только один ответ, который не дает никакого исходного кода. Я надеюсь на более подробные ответы.

Я использую 64-битный Linux (Ubuntu 12.04, если это имеет значение). Вот некоторый код, который распределяет страницу, записывает в нее некоторый 64-битный код и выполняет этот код.

#include <assert.h>
#include <malloc.h>
#include <stdio.h>
#include <sys/mman.h>  // mprotect
#include <unistd.h>  // sysconf

unsigned char test_function[] = { 0xC3 };  // RET
int main()
{
    int pagesize = sysconf(_SC_PAGE_SIZE);
    unsigned char *buffer = memalign(pagesize, pagesize);
    void (*func)() = (void (*)())buffer;

    memcpy(buffer, test_function, sizeof test_function);

    // func();  // will segfault 
    mprotect(buffer, pagesize, PROT_EXEC);
    func();  // works fine
}

Теперь, исключительно для развлечения, я хотел бы сделать то же самое, но с buffer содержащий произвольный 32-битный (ia32) код вместо 64-битного кода. Эта страница подразумевает, что вы можете выполнить 32-битный код на 64-битном процессоре, введя "подрежим длинной совместимости", установив биты дескриптора сегмента CS как LMA=1, L=0, D=1, Я готов обернуть мой 32-битный код в пролог / эпилог, который выполняет эту настройку.

Но могу ли я выполнить эту настройку в Linux в режиме пользователя? (Ответы BSD/Darwin также будут приняты.) Именно здесь я начинаю задумываться. Я думаю, что решение включает в себя добавление нового дескриптора сегмента в GDT (или это LDT?), А затем переключение на этот сегмент через lcall инструкция. Но можно ли все это сделать в пользовательском режиме?

Вот пример функции, которая должна возвращать 4 при успешном запуске в подрежиме совместимости и 8 при запуске в длинном режиме. Моя цель состоит в том, чтобы получить указатель инструкции, чтобы взять этот путь и выйти на другую сторону с %rax=4без перехода в режим ядра (или только через документированные системные вызовы).

unsigned char behave_differently_depending_on_processor_mode[] = {
    0x89, 0xE0,  // movl %esp, %eax
    0x56,        // push %{e,r}si
    0x29, 0xE0,  // subl %esp, %eax
    0x5E,        // pop %{e,r}si
    0xC3         // ret
};

1 ответ

Да, ты можешь. Это даже выполнимо, используя полностью поддерживаемые интерфейсы. Используйте modify_ldt для установки 32-битного сегмента кода в LDT, затем установите дальний указатель на ваш 32-битный код, а затем сделайте косвенный переход к нему, используя "ljumpl *(%eax)" в нотации AT&T.

Тем не менее, вы столкнетесь со всеми видами snafus. Старшие биты вашего указателя стека могут быть уничтожены. Вам, вероятно, нужен сегмент данных, если вы действительно хотите запустить реальный код. И вам нужно будет сделать еще один прыжок, чтобы вернуться в 64-битный режим.

Полностью проработанный пример приведен в моих linux-clock-tests в test_vsyscall.cc. (Это немного сломано на любом выпущенном ядре: int cc вылетит. Вы должны изменить это на что-то более умное, например, "nop". Посмотрите на intcc32.

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