Código de máquina 8086 (MS-DOS .COM), 83 bytes
Ejecutable en DOSBox o su motor informático de vapor favorito. La cadena a irradiar se proporciona como un argumento de línea de comando.
Binario:
00000000 : EB 28 28 8A 0E 80 00 49 BD 83 00 B4 02 51 8A 0E : .((....I.....Q..
00000010 : 80 00 BE 82 00 AC 39 EE 74 04 88 C2 CD 21 E2 F5 : ......9.t....!..
00000020 : 59 45 B2 0A CD 21 E2 E5 C3 90 EB D7 D7 8A 0E 80 : YE...!..........
00000030 : 00 49 BD 83 00 B4 02 51 8A 0E 80 00 BE 82 00 AC : .I.....Q........
00000040 : 39 EE 74 04 88 C2 CD 21 E2 F5 59 45 B2 0A CD 21 : 9.t....!..YE...!
00000050 : E2 E5 C3 : ...
Legible:
cpu 8086
org 0x100
jmp part2
db 0x28
part1:
mov cl, [0x80]
dec cx
mov bp, 0x83
mov ah, 0x02
.l:
push cx
mov cl, [0x80]
mov si, 0x82
.k:
lodsb
cmp si, bp
je .skip
mov dl, al
int 0x21
.skip:
loop .k
pop cx
inc bp
mov dl, 10
int 0x21
loop .l
ret
nop
part2:
jmp part1
db 0xd7
mov cl, [0x80]
dec cx
mov bp, 0x83
mov ah, 0x02
.l:
push cx
mov cl, [0x80]
mov si, 0x82
.k:
lodsb
cmp si, bp
je .skip
mov dl, al
int 0x21
.skip:
loop .k
pop cx
inc bp
mov dl, 10
int 0x21
loop .l
ret
En mal estado
La parte activa se duplica para que siempre haya una que no haya sido tocada por la radiación. Seleccionamos la versión saludable a modo de saltos. Cada salto es un salto corto, por lo que tiene solo dos bytes de longitud, donde el segundo byte es el desplazamiento (es decir, la distancia al salto, con el signo que determina la dirección).
Podemos dividir el código en cuatro partes que podrían irradiarse: salto 1, código 1, salto 2 y código 2. La idea es asegurarse de que siempre se use una parte limpia del código. Si se irradia una de las partes del código, se debe elegir la otra, pero si se irradia uno de los saltos, ambas partes del código estarán limpias, por lo que no importa cuál se elija.
La razón para tener dos partes de salto es detectar la irradiación en la primera parte saltando sobre ella. Si se irradia la primera parte del código, significa que llegaremos un byte fuera de la marca. Si nos aseguramos de que un aterrizaje fallido seleccione el código 2, y un aterrizaje adecuado seleccione el código 1, somos dorados.
Para ambos saltos, duplicamos el byte de desplazamiento, haciendo que cada parte del salto tenga 3 bytes de longitud. Esto asegura que la irradiación en uno de los dos últimos bytes todavía hará que el salto sea válido. La irradiación en el primer byte detendrá el salto, ya que los dos últimos bytes formarán una instrucción completamente diferente.
Da el primer salto:
EB 28 28 jmp +0x28 / db 0x28
Si 0x28
se elimina cualquiera de los bytes, seguirá saltando al mismo lugar. Si 0xEB
se elimina el byte, terminaremos con
28 28 sub [bx + si], ch
que es una instrucción benigna en MS-DOS (otros sabores pueden estar en desacuerdo), y luego pasamos al código 1, que debe estar limpio, ya que el daño estaba en el salto 1.
Si se da el salto, aterrizamos en el segundo salto:
EB D7 D7 jmp -0x29 / db 0xd7
Si esta secuencia de bytes está intacta, y aterrizamos justo en la marca, eso significa que el código 1 estaba limpio, y esta instrucción salta a esa parte. El byte de desplazamiento duplicado garantiza esto, incluso si uno de estos bytes de desplazamiento está dañado. Si aterrizamos un byte (debido a un código dañado 1 o salto 1) o el 0xEB
byte es el dañado, los dos bytes restantes también serán benignos:
D7 D7 xlatb / xlatb
Cualquiera que sea el caso, si terminamos ejecutando esas dos instrucciones, sabemos que el salto 1, el código 1 o el salto 2 fueron irradiados, lo que hace que la caída al código 2 sea segura.
Pruebas
El siguiente programa se utilizó para crear automáticamente todas las versiones del archivo .COM. También crea un archivo BAT que se puede ejecutar en el entorno de destino, que ejecuta cada binario irradiado y canaliza sus salidas a archivos de texto separados. Comparar los archivos de salida para validar es bastante fácil, pero DOSBox no lo tiene fc
, por lo que no se agregó al archivo BAT.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
FILE *fin, *fout, *fbat;
int fsize;
char *data;
if (!(fin = fopen(argv[1], "rb")))
{
fprintf(stderr, "Could not open input file \"%s\".\n", argv[1]);
exit(1);
}
if (!(fbat = fopen("tester.bat", "w")))
{
fprintf(stderr, "Could not create BAT test file.\n");
exit(2);
}
fseek(fin, 0L, SEEK_END);
fsize = ftell(fin);
fseek(fin, 0L, SEEK_SET);
if (!(data = malloc(fsize)))
{
fprintf(stderr, "Could not allocate memory.\n");
exit(3);
}
fread(data, 1, fsize, fin);
fprintf(fbat, "@echo off\n");
for (int i = 0; i < fsize; i++)
{
char fname[512];
sprintf(fname, "%03d.com", i);
fprintf(fbat, "%s Hello, world! > %03d.txt\n", fname, i);
fout = fopen(fname, "wb");
fwrite(data, 1, i, fout);
fwrite(data + i + 1, 1, fsize - i - 1, fout);
fclose(fout);
}
free(data);
fclose(fin);
fclose(fbat);
}