X86-32/x86-64 многоугольный фрагмент машинного кода, который обнаруживает 64-битный режим во время выполнения?

Можно ли для тех же байтов машинного кода выяснить, работают ли они в режиме 32 или 64 бит, а затем делать разные вещи?

то есть. напишите polyglot машинный код.

Обычно вы можете обнаружить во время сборки с помощью макросов #ifdef. Или в C вы можете написать if() с константой времени компиляции в качестве условия и укомплектовать компилятор с другой стороны.

Это полезно только для странных случаев, например, для инъекций кода, или просто для того, чтобы увидеть, возможно ли это.


См. также: машинный код polyglot ARM/x86 для перехода на разные адреса, в зависимости от того, какая архитектура декодирует байты.

Ответ 1

Самый простой способ - использование однобайтовых inc операций inc, которые повторно используются в качестве префиксов REX в 64-битном режиме. Префикс REX не влияет на jcc, так что вы можете сделать:

xor    eax,eax       ; clear ZF
db  0x40             ; 32bit: inc eax.   64bit: useless REX prefix
jz   .64bit_mode     ; REX jcc  works fine

См. Также 3-сторонний полиглот, который возвращает 16, 32 или 64 в соответствии с режимом, в котором он выполняется: Определите свою языковую версию на codegolf.SE.


Напоминание: обычно вы не хотите, чтобы это было частью скомпилированного двоичного файла. Обнаружение режима во время сборки, поэтому любое решение, основанное на этом, может оптимизироваться, а не приниматься во время выполнения. например, с #ifdef __x86_64__ и/или sizeof(void*) (но не забывайте, что в длинном режиме 32-битные указатели ILP32 x32 ABI).


Здесь полная программа Linux/NASM, которая использует syscall для exit(1) если она работает как 64-битная, или int 0x80 для exit(0) если она работает как 32-битная.

Использование BITS 32 и BITS 64 гарантирует, что он в любом случае собирается на один и тот же машинный код. (И да, я проверил с помощью objdump -d чтобы показать необработанные байты машинного кода)

Тем не менее, я использовал db 0x40 вместо inc eax, чтобы прояснить, что особенного.

BITS 32
global _start
_start:
        xor    eax,eax          ; clear ZF
        db 0x40                 ; 32bit: inc eax.  64bit: useless REX prefix
        jz      .64bit_mode     ; REX jcc  still works

        ;jmp .64bit_mode   ; uncomment to test that the 64bit code does fault in a 32bit binary

.32bit_mode:
        xor     ebx,ebx
        mov     eax, 1          ; exit(0)
        int     0x80


BITS 64
.64bit_mode:
        lea  rdx, [rel _start]      ; An instruction that won't assemble in 32-bit mode.
        ;; arbitrary 64bit code here

        mov  edi, 1
        mov  eax, 231    ;  exit_group(1).
        syscall          ; This does SIGILL if this is run in 32bit mode on Intel CPUs

;;;;; Or as a callable function:
BITS 32
am_i_32bit:  ;; returns false only in 64bit mode
        xor     eax,eax

        db 0x40                 ; 32bit: inc eax
                                ; 64bit: REX.W=0
        ;nop                     ; REX nop  is  REX xchg eax,eax
        ret                     ; REX ret works normally, too

Проверено и работает. Я строю его дважды, чтобы получить разные метаданные ELF для одного и того же машинного кода.

$ yasm -felf64 -Worphan-labels -gdwarf2 x86-polyglot-32-64.asm && ld -o x86-polyglot.64bit x86-polyglot-32-64.o
$ yasm -felf32 -Worphan-labels -gdwarf2 x86-polyglot-32-64.asm && ld -melf_i386 -o x86-polyglot.32bit x86-polyglot-32-64.o
$ ./x86-polyglot.32bit && echo 32bit || echo 64bit
32bit
$ ./x86-polyglot.64bit && echo 32bit || echo 64bit
64bit

(команды сборки из сборки 32-битных двоичных файлов в 64-битной системе (набор инструментов GNU), связанные с разделом часто задаваемых вопросов в вики-теге ).