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 2

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

Таким образом, вы можете ljmp к этому фрагменту кода и проверить (32-битное) возвращаемое значение; оно будет -1, Если выполняется в 64-битном режиме, но -2, Если выполняется в 32-битном режиме.

Я не знаю, каковы предпосылки для быстрого возврата из 32-битного режима в 64-битный. Я подозреваю, что вам, возможно, придется настроить как "low mem" 64-битный указатель стека для начала, так и low-mem 64-битный кодовый адрес "trampoline" (так что и возвращаемый EIP, и возвращаемый ESP в фрейме far call являются 32-битными значения).