В x86 / x86-64 разветвляются по-разному, используя только машинные видимые символы ASCII в машинном коде

14

Задача проста: написать программу, которая разветвляется по-разному в x86 (32-разрядный) и x86-64 (64-разрядный), используя только печатные видимые символы ASCII 0x21 ... 0x7e (пробел и del не допускаются) в машинном коде ,

  • Условная сборка не допускается.
  • Использование вызовов API в не разрешено.
  • Использование кода режима ядра (кольцо 0) не допускается.
  • Код должен работать без исключений как в IA-32, так и в x86-64 в Linux или в некоторых других ОС с защищенным режимом.
  • Функционирование не должно зависеть от параметров командной строки.
  • Все инструкции должны быть закодированы в машинном коде с использованием только символов ASCII в диапазоне 0x21 ... 0x7e (десятичное число 33 ... 126). Так, например. cpuidвыходит за пределы (это 0f a2), если вы не используете самоизменяющийся код.
  • Один и тот же двоичный код должен выполняться в x86 и x86-64, но поскольку заголовки файлов (ELF / ELF64 / и т. Д.) Могут отличаться, вам может понадобиться собрать и снова связать его. Однако двоичный код не должен изменяться.
  • Решения должны работать на всех процессорах между i386 ... Core i7, но меня также интересуют более ограниченные решения.
  • Код должен переходить в 32-разрядной версии x86, но не в x86-64, или наоборот, но использование условных переходов не является обязательным (также допускается косвенный переход или вызов). Целевой адрес ветвления должен быть таким, чтобы для некоторого кода было место, по крайней мере, 2 байта, в которые jmp rel8помещается функция jump ( ).

Победившим ответом является тот, который использует наименьшее количество байтов в машинном коде. Байты в заголовке файла (например, ELF / ELF64) не учитываются, и любые байты кода после ветви (для целей тестирования и т. Д.) Также не учитываются.

Пожалуйста, представьте свой ответ в виде ASCII, в виде шестнадцатеричных байтов и в виде комментария кода.

Мое решение, 39 байт:

ASCII: fhotfhatfhitfhutfhotfhatfhitfhut_H3<$t!

шестнадцатеричное: 66 68 6F 74 66 68 61 74 66 68 69 74 66 68 75 74 66 68 6F 74 66 68 61 74 66 68 69 74 66 68 75 74 5F 48 33 3C 24 74 21.

Код:

; can be compiled eg. with yasm.
; yasm & ld:
; yasm -f elf64 -m amd64 -g dwarf2 x86_x86_64_branch.asm -o x86_x86_64_branch.o; ld x86_x86_64_branch.o -o x86_x86_64_branch
; yasm & gcc:
; yasm -f elf64 -m amd64 -g dwarf2 x86_x86_64_branch.asm -o x86_x86_64_branch.o; gcc -o x86_x86_64_branch x86_x86_64_branch.o

section .text
global main
extern printf

main:
    push    word 0x746f     ; 66 68 6f 74 (x86, x86-64)
    push    word 0x7461     ; 66 68 61 74 (x86, x86-64)
    push    word 0x7469     ; 66 68 69 74 (x86, x86-64)
    push    word 0x7475     ; 66 68 75 74 (x86, x86-64)

    push    word 0x746f     ; 66 68 6f 74 (x86, x86-64)
    push    word 0x7461     ; 66 68 61 74 (x86, x86-64)
    push    word 0x7469     ; 66 68 69 74 (x86, x86-64)
    push    word 0x7475     ; 66 68 75 74 (x86, x86-64)

    db      0x5f            ; x86:    pop edi
                            ; x86-64: pop rdi

    db      0x48, 0x33, 0x3c, 0x24
                            ; x86:
                            ; 48          dec eax
                            ; 33 3c 24    xor edi,[esp]

                            ; x86-64:
                            ; 48 33 3c 24 xor rdi,[rsp]

    jz      @bits_64        ; 0x74 0x21
                            ; branch only if running in 64-bit mode.

; the code golf part ends here, 39 bytes so far.

; the rest is for testing only, and does not affect the answer.

    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop

    jmp     @bits_32

@bits_64:
    db      0x55                    ; push rbp

    db      0x48, 0x89, 0xe5        ; mov rbp,rsp
    db      0x48, 0x8d, 0x3c, 0x25  ; lea rdi,
    dd      printf_msg              ; [printf_msg]
    xor     eax,eax
    mov     esi,64

    call    printf
    db      0x5d                    ; pop rbp

    NR_exit equ 60

    xor     edi,edi
    mov     eax,NR_exit     ; number of syscall (60)
    syscall

@bits_32:
    lea     edi,[printf_msg]
    mov     esi,32
    call    printf

    mov     eax,NR_exit
    int     0x80

section .data

printf_msg: db "running in %d-bit system", 0x0a, 0
NRZ
источник
1
Вы действительно ударили по горячей шляпе в хижине :)
адицу ушел, потому что SE это ЗЛО
Ницца. Странно, но приятно. Поскольку вы устанавливаете выигрышное условие как «самое короткое», я собираюсь изменить тег на [code-golf] и добавить несколько новых описательных тегов. Если они вам не нравятся, дайте мне знать.
dmckee --- котенок экс-модератора

Ответы:

16

7 байт

0000000: 6641 2521 2173 21                        fA%!!s!

Как 32 бит

00000000  6641              inc cx
00000002  2521217321        and eax,0x21732121

Как 64 бит

00000000  6641252121        and ax,0x2121
00000005  7321              jnc 0x28

andочищает флаг переноса, поэтому 64-битная версия всегда переходит. Для 64-битных 6641это переопределение размера операнда, за которым следует rex.bразмер операнда для and16-битного значения. На 32-битной 6641инструкции является полной, поэтому andне имеет префикса и имеет размер 32-битного операнда. Это изменяет количество непосредственных байтов, потребляемых andпредоставлением двух байтов инструкций, которые выполняются только в 64-битном режиме.

Джефф Риди
источник
1
Поздравляю при достижении 1к.
DavidC
это поведение зависит от процессора. Некоторые 64-битные системы игнорируют префикс 66 в 64-битном режиме.
Питер Ферри
@peterferrie У вас есть ссылка на это? Мое чтение состоит в том, что префикс 66h игнорируется, когда REX.W установлен, но это имеет только REX.B
Джефф Риди
извините, я ошибаюсь. Это влияет только на передачу контроля (например, 66 e8 не переключается на 16-битный IP на Intel).
Питер Ферри
7

11 байт

ascii: j6Xj3AX,3t!
hex: 6a 36 58 6a 33 41 58 2c 33 74 21

Использует тот факт, что в 32-битном 0x41 является просто inc %ecx, в то время как в 64-битном это raxпрефикс, который изменяет целевой регистр следующей popинструкции.

        .globl _check64
_check64:
        .byte   0x6a, 0x36      # push $0x36
        .byte   0x58            # pop %rax
        .byte   0x6a, 0x33      # push $0x33

        # this is either "inc %ecx; pop %eax" in 32-bit, or "pop %r8" in 64-bit.
        # so in 32-bit it sets eax to 0x33, in 64-bit it leaves rax unchanged at 0x36.
        .byte   0x41            # 32: "inc %ecx", 64: "rax prefix"
        .byte   0x58            # 32: "pop %eax", 64: "pop %r8"

        .byte   0x2c, 0x33      # sub $0x33,%al
        .byte   0x74, 0x21      # je (branches if 32 bit)

        mov     $1,%eax
        ret

        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        mov     $0,%eax
        ret

Написал это на OSX, ваш ассемблер может отличаться.

Назовите это с этим:

#include <stdio.h>
extern int check64(void);
int main(int argc, char *argv[]) {
  if (check64()) {
    printf("64-bit\n");
  } else {
    printf("32-bit\n");
  }
  return 0;
}
Кит Рэндалл
источник
2

7 байт

Не полагаясь на префикс 66.

$$@$Au!

32-бит:

00000000 24 24 and al,24h
00000002 40    inc eax
00000003 24 41 and al,41h
00000005 75 21 jne 00000028h

AL будет иметь бит 0, установленный после INC, второй AND сохранит его, ветвь будет принята.

64-бит:

00000000 24 24    and al,24h
00000002 40 24 41 and al,41h
00000005 75 21    jne 00000028h

AL будет иметь бит 0, очищенный после первого AND, ветвь не будет взята.

Питер Ферри
источник
0

Если бы только C9h можно было печатать ...

32-бит:

00000000 33 C9 xor  ecx, ecx
00000002 63 C9 arpl ecx, ecx
00000004 74 21 je   00000027h

ARPL очистит флаг Z, в результате чего ветвь будет занята.

64-бит:

00000000 33 C9 xor    ecx, ecx
00000002 63 C9 movsxd ecx, ecx
00000004 74 21 je     00000027h

XOR установит флаг Z, MOVSXD не изменит его, ветвь не будет взята.

Питер Ферри
источник