Ramifique de maneira diferente em x86 / x86-64 usando apenas caracteres ASCII visíveis imprimíveis no código da máquina

14

A tarefa é simples: escreva um programa que se ramifique de maneira diferente em x86 (32 bits) e x86-64 (64 bits) usando apenas caracteres ASCII visíveis e imprimíveis 0x21 ... 0x7e (space e del não são permitidos) no código da máquina .

  • Montagem condicional não é permitida.
  • O uso de chamadas de API não é permitido.
  • O uso do código no modo kernel (anel 0) não é permitido.
  • O código deve ser executado sem causar exceções no IA-32 e no x86-64 no Linux ou em outro sistema operacional em modo protegido.
  • O funcionamento não deve depender dos parâmetros da linha de comando.
  • Todas as instruções devem ser codificadas no código da máquina usando apenas caracteres ASCII no intervalo 0x21 ... 0x7e (33 ... 126 decimal). Então, por exemplo. cpuidestá fora dos limites (é 0f a2), a menos que você use código auto-modificável.
  • O mesmo código binário deve ser executado em x86 e x86-64, mas como os cabeçalhos de arquivo (ELF / ELF64 / etc.) Podem ser diferentes, talvez seja necessário montar e vincular novamente. No entanto, o código binário não deve ser alterado.
  • As soluções devem funcionar em todos os processadores entre o i386 e o ​​Core i7, mas também estou interessado em soluções mais limitadas.
  • O código deve ramificar-se no x86 de 32 bits, mas não no x86-64, ou vice-versa, mas o uso de saltos condicionais não é um requisito (salto ou chamada indireta também é aceita). O endereço de destino da ramificação deve ser tal que exista espaço para algum código, pelo menos 2 bytes de espaço no qual um salto curto ( jmp rel8) se encaixa.

A resposta vencedora é a que usa menos bytes no código da máquina. Os bytes no cabeçalho do arquivo (ELF / ELF64, por exemplo) não são contados, e os bytes de código após a ramificação (para fins de teste, etc.) também não são contados.

Apresente sua resposta como ASCII, como bytes hexadecimais e como código comentado.

Minha solução, 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
fonte
1
Você realmente acertou o chapéu quente na cabana :)
aditsu saiu porque o SE é MAU '03/
Agradável. Estranho, mas legal. Como você está definindo a condição vencedora como "mais curta", vou mudar a tag para [code-golf] e adicionar algumas novas tags descritivas. Se você não gostar, me avise.
dmckee --- ex-moderador gatinho

Respostas:

16

7 bytes

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

Como 32 bits

00000000  6641              inc cx
00000002  2521217321        and eax,0x21732121

Como 64 bits

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

andlimpa a bandeira de transporte para que a versão de 64 bits sempre salte. Para 64 bits, 6641é a substituição do tamanho do operando, seguida pelo rex.btamanho do operando para and16 bits. Em 32 bits, 6641é uma instrução completa, portanto andnão possui prefixo e possui um tamanho de operando de 32 bits. Isso altera o número de bytes imediatos consumidos, andfornecendo dois bytes de instruções que são executadas apenas no modo de 64 bits.

Geoff Reedy
fonte
1
Parabéns por atingir 1k.
21413
esse comportamento é específico da CPU. Alguns sistemas de 64 bits ignoram o prefixo 66 no modo de 64 bits.
peter ferrie
@peterferrie Você tem uma referência para isso? Minha leitura é que um prefixo 66h é ignorado quando REX.W está definido, mas isso só tem REX.B
Geoff Reedy
com licença, estou errado. É apenas a transferência de controle que é afetada dessa maneira (por exemplo, o 66 e8 não muda para o IP de 16 bits na Intel).
peter ferrie
7

11 bytes

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

Usa o fato de que em 32 bits, 0x41 é justo inc %ecx, enquanto em 64 bits é o raxprefixo que modifica o registro de destino da popinstrução a seguir .

        .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

Escreveu isso no OSX, seu montador pode ser diferente.

Chame assim:

#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
fonte
2

7 bytes

Não confiando no 66 prefixo.

$$@$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 terá o bit 0 definido após o INC, o segundo AND o preservará e a ramificação será realizada.

64 bits:

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

AL terá o bit 0 claro após o primeiro AND, a ramificação não será aceita.

Peter Ferrie
fonte
0

Se apenas C9h fosse imprimível ...

32 bits:

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

O ARPL limpará o sinalizador Z, fazendo com que o ramo seja executado.

64 bits:

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

O XOR definirá o sinalizador Z, o MOVSXD não o mudará, o ramo não será utilizado.

Peter Ferrie
fonte