Far call в USER32 CS из 64-битного кода на Linux
Недавно я понял, что это можно сделать в 64-битном коде:
const size_t kLowStackSize = 1024UL * 1024UL * 4UL;
void *low_stack = mmap(NULL, kLowStackSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
struct __attribute__((packed, aligned(16))) {
int32_t address;
int16_t segment;
} target = {(uint32_t) (uint64_t) code, 0x23};
asm volatile(
"mov %%rsp, %%r8n"
"mov %[stack], %%rspn"
"push %%r8n"
"lcall *(%[target])n"
"pop %%rsp"
:
: [stack] "r" (low_stack + kLowStackSize), [target] "r" (&target)
: "r8");
Где code
указывает на фрагмент 32-битного кода, расположенный на исполняемой странице в Нижнем 4GiB адресного пространства, а 0x23
- это значение селектора сегментов __USER32_CS
в заголовках x86 Linux. Я не знаю, нужны ли атрибуты для цели прыжка, но я добавил их для хорошей меры. Конечно, чтобы сделать возможным далекое возвращение, сам этот вызывающий код должен быть расположен где-то в Нижнем 4 Гибе виртуальное адресное пространство. Я обнаружил, что достаточно поместить его в main
.
Я понимаю, что это в основном бесполезно (нет загруженных 32-битных библиотек, разные соглашения о вызовах и т. д.) и склонны к поломке(значение __USER32_CS
не является частью пользовательского интерфейса Linux).
Мой вопрос: Есть ли простой способ продемонстрировать, что цель вызова действительно выполняется в 32-битном режиме? Есть ли какие-либо практические применения (существующее программное обеспечение библиотек, использующих его, или, по крайней мере, не такие уж непрактичные возможности) для такого рода звонков?
1 ответ:
В x86 32-битные и 64-битные кодировки команд в основном идентичны.
Большим исключением из этого правила являются 16 однобайтовых опкодов командINC
иDEC
. Эти 16 байт в 64-битном режиме были перепрофилированы в семейство префиксовREX
, что позволяет задать размер 64-битного операнда, а также использование новых регистров в 64-битном режиме.Это означает 64-битный код, такой как:
xorl %eax, %eax .byte 0x48, 0xff, 0xc8 ; this is the same as: ; decq %rax ; opcode: 0x48 0xff 0xc8 lret $0
Допустим 32-битный код, но будет ли он выполнен как:
xorl %eax, %eax decl %eax ; opcode: 0x48 decl %eax ; opcode: 0xff 0xc8 lret $0
Таким образом, вы можете
Я не знаю, каковы предпосылки для быстрого возврата из 32-битного режима в 64-битный. Я подозреваю, что вам, возможно, придется настроить как "low mem" 64-битный указатель стека для начала, так и low-mem 64-битный кодовый адрес "trampoline" (так что и возвращаемый EIP, и возвращаемый ESP в фрейме far call являются 32-битными значения).ljmp
к этому фрагменту кода и проверить (32-битное) возвращаемое значение; оно будет-1
, Если выполняется в 64-битном режиме, но-2
, Если выполняется в 32-битном режиме.