Concurso de Código C ofuscado 2006. Por favor, explique sykes2.c
obfuscation deobfuscation (4)
¿Cómo funciona este programa C?
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">''txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
Se compila como está (probado en gcc 4.6.3
). Imprime el tiempo cuando se compila. En mi sistema:
!! !!!!!! !! !!!!!! !! !!!!!!
!! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !!
!! !!!!!! !! !! !! !! !! !!!!!!
!! !! !! !! !! !! !!
!! !! !! !! !! !! !!
!! !!!!!! !! !! !! !!!!!!
Fuente: sykes2: un reloj en una línea , sugerencias del autor de sykes2
Algunos consejos: No hay advertencias de compilación por defecto. Compilado con -Wall
, se -Wall
las siguientes advertencias:
sykes2.c:1:1: warning: return type defaults to ‘int’ [-Wreturn-type]
sykes2.c: In function ‘main’:
sykes2.c:1:14: warning: value computed is not used [-Wunused-value]
sykes2.c:1:1: warning: implicit declaration of function ‘putchar’ [-Wimplicit-function-declaration]
sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of ‘|’ [-Wparentheses]
sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of ‘|’ [-Wparentheses]
sykes2.c:1:1: warning: control reaches end of non-void function [-Wreturn-type]
Agregando a las otras soluciones, -~x
es igual a x+1
porque ~x
es equivalente a (0xffffffff-x)
. Esto es igual a (-1-x)
en el complemento de 2s, entonces -~x
es -(-1-x) = x+1
.
Des-ofuscado el módulo aritmético tanto como pude y eliminé la reccursión
int pixelX, line, digit ;
for(line=6; line >= 0; line--){
for (digit =0; digit<8; digit++){
for(pixelX=7;pixelX > 0; pixelX--){
putchar('' ''| 1 + ">''txiZ^(~z?"["12:34:56"[digit]-''0''] >>
(";;;====~$::199"[pixel*2 & 8 | line] / (pixelX&2 ? 1 : 8) ) % 8 & 1);
}
}
putchar(''/n'');
}
Expandiéndolo un poco más:
int pixelX, line, digit, shift;
char shiftChar;
for(line=6; line >= 0; line--){
for (digit =0; digit<8; digit++){
for(pixelX=7;pixelX >= 0; pixelX--){
shiftChar = ";;;====~$::199"[pixelX*2 & 8 | line];
if (pixelX & 2)
shift = shiftChar & 7;
else
shift = shiftChar >> 3;
putchar('' ''| (">''txiZ^(~z?"["12:34:56"[digit]-''0''] + 1) >> shift & 1 );
}
}
putchar(''/n'');
}
Vamos a desenfocarlo.
Sangría
main(_) {
_^448 && main(-~_);
putchar(--_%64
? 32 | -~7[__TIME__-_/8%8][">''txiZ^(~z?"-48] >> ";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1
: 10);
}
Introduciendo variables para desenredar este lío:
main(int i) {
if(i^448)
main(-~i);
if(--i % 64) {
char a = -~7[__TIME__-i/8%8][">''txiZ^(~z?"-48];
char b = a >> ";;;====~$::199"[i*2&8|i/64]/(i&2?1:8)%8;
putchar(32 | (b & 1));
} else {
putchar(10); // newline
}
}
Tenga en cuenta que -~i == i+1
debido a dos complementos. Por lo tanto, tenemos
main(int i) {
if(i != 448)
main(i+1);
i--;
if(i % 64 == 0) {
putchar(''/n'');
} else {
char a = -~7[__TIME__-i/8%8][">''txiZ^(~z?"-48];
char b = a >> ";;;====~$::199"[i*2&8|i/64]/(i&2?1:8)%8;
putchar(32 | (b & 1));
}
}
Ahora, tenga en cuenta que a[b]
es lo mismo que b[a]
, y aplique el cambio -~ == 1+
nuevo:
main(int i) {
if(i != 448)
main(i+1);
i--;
if(i % 64 == 0) {
putchar(''/n'');
} else {
char a = (">''txiZ^(~z?"-48)[(__TIME__-i/8%8)[7]] + 1;
char b = a >> ";;;====~$::199"[(i*2&8)|i/64]/(i&2?1:8)%8;
putchar(32 | (b & 1));
}
}
Convertir la recursión en un bucle y escabullirse en un poco más de simplificación:
// please don''t pass any command-line arguments
main() {
int i;
for(i=447; i>=0; i--) {
if(i % 64 == 0) {
putchar(''/n'');
} else {
char t = __TIME__[7 - i/8%8];
char a = ">''txiZ^(~z?"[t - 48] + 1;
int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
if((i & 2) == 0)
shift /= 8;
shift = shift % 8;
char b = a >> shift;
putchar(32 | (b & 1));
}
}
}
Esto genera un carácter por iteración. Cada 64 caracteres, genera una nueva línea. De lo contrario, utiliza un par de tablas de datos para averiguar qué generar y coloca el carácter 32 (un espacio) o el carácter 33 (a !
). La primera tabla ( ">''txiZ^(~z?"
) Es un conjunto de 10 mapas de bits que describen la apariencia de cada carácter, y la segunda tabla ( ";;;====~$::199"
) selecciona la bit apropiado para mostrar desde el mapa de bits.
La segunda mesa
Comencemos por examinar la segunda tabla, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
. i/64
es el número de línea (6 a 0) e i*2&8
es 8 si i
es 4, 5, 6 o 7 mod 8.
if((i & 2) == 0) shift /= 8; shift = shift % 8
if((i & 2) == 0) shift /= 8; shift = shift % 8
selecciona el dígito octal alto (para i%8
= 0,1,4,5) o el dígito octal bajo (para i%8
= 2,3,6,7) del valor de la tabla. La mesa de cambios termina pareciéndose a esto:
row col val
6 6-7 0
6 4-5 0
6 2-3 5
6 0-1 7
5 6-7 1
5 4-5 7
5 2-3 5
5 0-1 7
4 6-7 1
4 4-5 7
4 2-3 5
4 0-1 7
3 6-7 1
3 4-5 6
3 2-3 5
3 0-1 7
2 6-7 2
2 4-5 7
2 2-3 3
2 0-1 7
1 6-7 2
1 4-5 7
1 2-3 3
1 0-1 7
0 6-7 4
0 4-5 4
0 2-3 3
0 0-1 7
o en forma tabular
00005577
11775577
11775577
11665577
22773377
22773377
44443377
Tenga en cuenta que el autor usó el terminador nulo para las dos primeras entradas de la tabla (¡furtivo!).
Esto está diseñado después de una pantalla de siete segmentos, con 7
s como espacios en blanco. Por lo tanto, las entradas en la primera tabla deben definir los segmentos que se iluminan.
La primera mesa
__TIME__
es una macro especial definida por el preprocesador. Se expande a una constante de cadena que contiene el tiempo en el que se ejecutó el preprocesador, en la forma "HH:MM:SS"
. Observe que contiene exactamente 8 caracteres. Tenga en cuenta que 0-9 tienen valores ASCII 48 a 57 y :
tiene valor ASCII 58. La salida es de 64 caracteres por línea, por lo que deja 8 caracteres por carácter de __TIME__
.
7 - i/8%8
es, por lo tanto, el índice de __TIME__
que se está __TIME__
actualmente (el 7-
es necesario porque estamos iterando i
hacia abajo). Entonces, t
es el carácter de __TIME__
está __TIME__
.
a
termina igualando lo siguiente en binario, dependiendo de la entrada t
:
0 00111111
1 00101000
2 01110101
3 01111001
4 01101010
5 01011011
6 01011111
7 00101001
8 01111111
9 01111011
: 01000000
Cada número es un mapa de bits que describe los segmentos que se iluminan en nuestra pantalla de siete segmentos. Dado que todos los caracteres son ASCII de 7 bits, el bit alto siempre se borra. Por lo tanto, 7
en la tabla de segmentos siempre se imprime como un espacio en blanco. La segunda tabla se ve así con los 7
s como espacios en blanco:
000055
11 55
11 55
116655
22 33
22 33
444433
Entonces, por ejemplo, 4
es 01101010
(bits 1, 3, 5 y 6 establecidos), que se imprime como
----!!--
!!--!!--
!!--!!--
!!!!!!--
----!!--
----!!--
----!!--
Para mostrar que entendemos realmente el código, ajustemos un poco la salida con esta tabla:
00
11 55
11 55
66
22 33
22 33
44
Esto se codifica como "?;;?==? ''::799/x07"
. Para fines artísticos, agregaremos 64 a algunos de los caracteres (ya que solo se usan los 6 bits bajos, esto no afectará la salida); esto da "?{{?}}?gg::799G"
(tenga en cuenta que el octavo carácter no está en uso, por lo que podemos hacer lo que queramos). Poniendo nuestra nueva tabla en el código original:
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">''txiZ^(~z?"-48]>>"?{{?}}?gg::799G"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
obtenemos
!! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !!
tal como esperábamos. No es tan sólido como el original, lo que explica por qué el autor eligió usar la tabla que usó.
Vamos a formatear esto para facilitar la lectura:
main(_){
_^448&&main(-~_);
putchar((--_%64) ? (32|-(~7[__TIME__-_/8%8])[">''txiZ^(~z?"-48]>>(";;;====~$::199")[_*2&8|_/64]/(_&2?1:8)%8&1):10);
}
Entonces, ejecutándolo sin argumentos, _ (argc convencionalmente) es 1
. main()
se llamará a sí mismo de forma recursiva, pasando el resultado de -(~_)
(NO en el modo de bits negativo en _
), por lo que realmente pasarán 448 recursiones (condición única donde _^448 == 0
).
Tomando eso, imprimirá 7 líneas anchas de 64 caracteres (la condición ternaria externa, y 448/64 == 7
). Así que vamos a reescribirlo un poco más limpio:
main(int argc) {
if (argc^448) main(-(~argc));
if (argc % 64) {
putchar((32|-(~7[__TIME__-argc/8%8])[">''txiZ^(~z?"-48]>>(";;;====~$::199")[argc*2&8|argc/64]/(argc&2?1:8)%8&1));
} else putchar(''/n'');
}
Ahora, 32
es decimal para el espacio ASCII. Imprime un espacio o un ''!'' (33 es ''!'', De ahí el '' &1
'' al final). Centrémonos en el blob en el medio:
-(~(7[__TIME__-argc/8%8][">''txiZ^(~z?"-48]) >>
(";;;====~$::199"[argc*2&8|argc/64]) / (argc&2?1:8) % 8
Como dijo otro póster, __TIME__
es el tiempo de compilación para el programa, y es una cadena, por lo que hay algo de aritmética de cadenas, además de aprovechar un subíndice de matriz bidireccional: a [b] es lo mismo que b [a ] para matrices de caracteres.
7[__TIME__ - (argc/8)%8]
Esto seleccionará uno de los primeros 8 caracteres en __TIME__
. Luego se indexa en [">''txiZ^(~z?"-48]
(0-9 caracteres son 48-57 decimales). Los caracteres en esta cadena deben haber sido elegidos para sus valores ASCII. Este mismo código ASCII de caracteres la manipulación continúa a través de la expresión, dando como resultado la impresión de un '''' o ''!'', dependiendo de la ubicación dentro del glifo del personaje.