Se ramifica de manera diferente en x86 / x86-64 usando solo caracteres ASCII visibles imprimibles en el código de máquina

14

La tarea es simple: escriba un programa que se bifurque de manera diferente en x86 (32 bits) y x86-64 (64 bits) utilizando solo caracteres ASCII visibles imprimibles 0x21 ... 0x7e (no se permiten espacios y del) en el código de máquina .

  • El montaje condicional no está permitido.
  • El uso de llamadas API no está permitido.
  • El uso del código de modo kernel (anillo 0) no está permitido.
  • El código debe ejecutarse sin causar excepciones tanto en IA-32 como en x86-64 en Linux o en algún otro sistema operativo en modo protegido.
  • El funcionamiento no debe depender de los parámetros de la línea de comandos.
  • Todas las instrucciones deben estar codificadas en código de máquina utilizando solo caracteres ASCII en el rango de 0x21 ... 0x7e (33 ... 126 decimal). Entonces, por ejemplo. cpuidestá fuera de los límites (es 0f a2), a menos que use un código de modificación automática.
  • El mismo código binario debe ejecutarse en x86 y x86-64, pero como los encabezados de los archivos (ELF / ELF64 / etc.) pueden ser diferentes, es posible que deba ensamblarlo y vincularlo nuevamente. Sin embargo, el código binario no debe cambiar.
  • Las soluciones deberían funcionar en todos los procesadores entre i386 ... Core i7, pero también estoy interesado en soluciones más limitadas.
  • El código debe bifurcarse en x86 de 32 bits pero no en x86-64, o viceversa, pero no es obligatorio usar saltos condicionales (también se acepta salto o llamada indirecta). La dirección de destino de la rama debe ser tal que haya espacio para algún código, al menos 2 bytes de espacio en el que jmp rel8encaje un salto corto ( ).

La respuesta ganadora es la que usa menos bytes en el código de máquina. Los bytes en el encabezado del archivo (ELF / ELF64, por ejemplo) no se cuentan, y los bytes de código después de la rama (para fines de prueba, etc.) tampoco se cuentan.

Presente su respuesta como ASCII, como bytes hexadecimales y como código comentado.

Mi solución, 39 bytes:

ASCII: fhotfhatfhitfhutfhotfhatfhitfhut_H3<$t!

hexadecimal: 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.

Código:

; 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
fuente
1
Realmente golpeaste el sombrero caliente en la cabaña :)
Aditsu se retiró porque SE es MAL
Agradable. Extraño, pero agradable. Como está estableciendo la condición ganadora como "más corta", voy a cambiar la etiqueta a [code-golf] y agregaré algunas nuevas etiquetas descriptivas. Si no te gustan, avísame.
dmckee --- ex-gatito moderador el

Respuestas:

16

7 bytes

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

Como 32 bits

00000000  6641              inc cx
00000002  2521217321        and eax,0x21732121

Como 64 bit

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

andborra la bandera de transporte para que la versión de 64 bits siempre salte. Para 64 bits, 6641es la anulación del tamaño del operando seguido de rex.bmodo que el tamaño del operando andsale como 16 bits. En 32 bits, 6641es una instrucción completa, por lo que andno tiene prefijo y tiene un tamaño de operando de 32 bits. Esto cambia el número de bytes inmediatos consumidos al anddar dos bytes de instrucciones que solo se ejecutan en modo de 64 bits.

Geoff Reedy
fuente
1
Felicidades por llegar a 1k.
DavidC
Este comportamiento es específico de la CPU. Algunos sistemas de 64 bits ignorarán el prefijo 66 en el modo de 64 bits.
Peter Ferrie
@peterferrie ¿Tiene una referencia para eso? Mi lectura es que un prefijo de 66h se ignora cuando se establece REX.W pero esto solo tiene REX.B
Geoff Reedy
disculpe, estoy equivocado. Solo la transferencia de control se ve afectada de esa manera (por ejemplo, 66 e8 no cambia a IP de 16 bits en Intel).
Peter Ferrie
7

11 bytes

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

Utiliza el hecho de que en 32 bits, 0x41 es justo inc %ecx, mientras que en 64 bits es el raxprefijo el que modifica el registro de destino de la siguiente popinstrucción.

        .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

Escribió esto en OSX, su ensamblador podría ser diferente.

Llámalo con esto:

#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;
}
Keith Randall
fuente
2

7 bytes

No depender del prefijo 66.

$$@$Au!

32 bits:

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

AL tendrá el bit 0 establecido después del INC, el segundo Y lo preservará, se tomará la rama.

64 bits:

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

AL tendrá el bit 0 claro después del primer AND, no se tomará la rama.

Peter Ferrie
fuente
0

Si solo C9h fuera imprimible ...

32 bits:

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

El ARPL borrará la bandera Z, haciendo que se tome la rama.

64 bits:

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

El XOR establecerá la bandera Z, el MOVSXD no lo cambiará, la rama no se tomará.

Peter Ferrie
fuente