¿Cómo funciona este JavaScript ofuscado?

93

¿Cómo funciona el siguiente JavaScript?

Entiendo que es un código minificado. He intentado desenfocarlo un poco, pero no puedo tener una idea clara de cómo logra este efecto. Puedo ver que está usando Strings para iteraciones de algún tipo, uso del objeto Date, manipulación extraña de cadenas, funciones matemáticas, luego el código se imprime solo.

¿Cómo podría reescribirse el mismo efecto con un ejemplo mínimo?

eval(z='p="<"+"pre>"/* ,.oq#+     ,._, */;for(y in n="zw24l6k\
4e3t4jnt4qj24xh2 x/* =<,m#F^    A W###q. */42kty24wrt413n243n\
9h243pdxt41csb yz/* #K       q##H######Am */43iyb6k43pk7243nm\
r24".split(4)){/* dP      cpq#q##########b, */for(a in t=pars\
eInt(n[y],36)+/*         p##@###YG=[#######y */(e=x=r=[]))for\
(r=!r,i=0;t[a/*         d#qg `*PWo##q#######D */]>i;i+=.05)wi\
th(Math)x-= /*        aem1k.com Q###KWR#### W[ */.05,0>cos(o=\
new Date/1e3/*      .Q#########Md#.###OP  A@ , */+x/PI)&&(e[~\
~(32*sin(o)*/* ,    (W#####Xx######.P^     T % */sin(.5+y/7))\
+60] =-~ r);/* #y    `^TqW####P###BP           */for(x=0;122>\
x;)p+="   *#"/* b.        OQ####x#K           */[e[x++]+e[x++\
]]||(S=("eval"/* l         `X#####D  ,       */+"(z=\'"+z.spl\
it(B = "\\\\")./*           G####B" #       */join(B+B).split\
(Q="\'").join(B+Q/*          VQBP`        */)+Q+")//m1k")[x/2\
+61*y-1]).fontcolor/*         TP         */(/\\w/.test(S)&&"#\
03B");document.body.innerHTML=p+=B+"\\n"}setTimeout(z)')//

JSFiddle

Alejandro
fuente
8
Animación genial ... ¡podría terminar usándola en algún lugar en realidad!
tymeJV
7
Oh bien. No noté el violín.
ThiefMaster
37
Esto se llama Quine, y es uno de los Quine más fantásticos que he visto en mi vida. en.wikipedia.org/wiki/Quine_(computing)
David Souther
9
@Roko C. Buljan creo que esta es su página: aem1k.com
Alexander
5
Parece que el autor ha puesto una versión anotada en GitHub.
Der Hochstapler

Respuestas:

67

Prólogo : embellecí y anoté el código extensamente en http://jsfiddle.net/WZXYr/2/

Considere la capa más externa:

eval(z = '...');

Una cadena de código se almacena en la variable z. El operador de asignación devuelve el valor asignado, por lo que la cadena de código también se pasa como argumento a eval.

La cadena de código se zejecuta dentro de eval. El código es extremadamente obtuso, incluso cuando se limpia, pero parece:

  1. Analice una cadena de números de base 36, delineados por el carácter 4.
  2. Rellenar un mapa de valores, utilizando las variables globales e, xy yal estado de espera mapa. El estado del mapa es, en parte, una función del segundo actual en el reloj de pared ( new Date / 1e3).
  3. Usando los valores del mapa, el código genera una cadena de salida, p
    • el código usa p += " *#"[index]para decidir si usar un espacio, asterisco o marca de almohadilla, donde indexestá realmente e[x++] + e[x++](como se dijo anteriormente, ey xson responsables del estado del mapa)
    • si el índice es mayor que la longitud de " *#", hay un código de reserva que llena la salida pcon caracteres de z. Los personajes internos se rellenan con personajes de animación, mientras que los personajes externos se extraen z.

Al final del código, hay una llamada a setTimeout(z), que evalúa de forma asincrónica la cadena de código z. Esta repetición de la invocación de zpermite que el código se repita .

Ejemplo simple:

Aquí hay una versión súper simple ( http://jsfiddle.net/5QXn8/ ):

eval(z='p="<"+"pre>";for(i=0;i<172;++i)if(i > 62 && i < 67)p+="!---"[~~(new Date/1e2 + i)%4];else p += ("eval(z=\'" + z + "\')")[i];document.body.innerHTML = p;setTimeout(z)')
  1. El forbucle agrega cada carácter a la cadena de salida p(la cadena tiene 172 caracteres de longitud):

    for(i=0;i<172;++i)
  2. El condicional interno decide si estamos en un personaje entre la posición 62 a 67, que son los personajes animados:

    if(i > 62 && i < 67)
  3. Si es así, imprima !---, desplazado en función de la décima parte del segundo valor del reloj de pared. Esto proporciona el efecto de animación.

    p+="!---"[~~(new Date/1e2 + i)%4]

    (Toda la maldad que hay alrededor new Dateestá realmente ahí para transformar un valor de fecha en un número entre 0 y 3.)

  4. De lo contrario, si no estamos en un personaje animado, imprima el icarácter índice de la cadena definida por

    "eval(z='" + z + "')"

    Es decir, la cadena de código zrodeada por eval('y ').

  5. Finalmente, genere la cadena y úsela setTimeoutpara poner en cola otra ejecución de z:

    document.body.innerHTML = p;setTimeout(z)

Tenga en cuenta que mi resultado final no es del todo correcto, no he tenido en cuenta las barras invertidas hacia el final, pero aún así debería darle una idea bastante buena de cómo funciona la técnica en general.

apsillers
fuente
8
Tenga en cuenta esto github.com/aemkei/world/blob/master/annotated.js - la propia versión anotada del autor en GitHub.
Benjamin Gruenbaum
36

Aquí está la fuente anotada. Ps: soy el autor;)

function z(){                     // will be replaced with eval

  p = "<" + "pre>";               // use <pre> tag for formatted output

  for (                           // loop though lines
    y in n = (                    // y - the line number
      "zw24"      +               // n - the encoded data
      "l6k4"      +               // every line holds encoded data
      "e3t4"      +
      "jnt4"      +               // string will be concated in build process
      "qj24"      +
      "xh2  4"    +               // data after spaces will be ignored but
      "2kty24"    +               // … is used to not break block comments
      "wrt4"      +               // … which will save some chars
      "13n24"     +
      "3n9h24"    +
      "3pdxt4"    +
      "1csb   4"  +
      "3iyb6k4"   +
      "3pk724"    +
      "3nmr24"
    ).split(4)                    // data will be split by (unused) 4

  ){
    for (                         // loop throug every char in line
      a in t = parseInt(          // numbers are encoded as string
        n[y],                     // … with a base of 36
        36
      ) + (                       // large number will be converted to string
        e =                       // e - holds the rendered globe
        x =                       // x - horizonal position
        r = []                    // r - bitmap flag if pixel is set
      )
    ){
      r = !r;                     // toggle binary flag

      for (                       // look though bitmap states
        i = 0;                 
        t[a] > i;                 // draw pixel t[a]-times
        i += .05
      )
        with (Math)               // refer to Math later
          x -= .05,
          0 > cos(                // prevent backface visibility
            o =
              new Date / 1e3 +    // get rotation based on current time
              x / PI
          ) && (
            e[                    // access matrix
              ~~(                 // convert float to integer
                sin(o) *          // rotate around y axis
                sin(.5 + y/7) *
                32                // scale up the globe
              ) + 60              // move to center
            ] = -~r               // store bitmap state in render matrix
          )
    }

    for (                         // loop through columns
      x = 0;
      122 > x;                    // break after char 122
    ) p += "   *#"[               // add space, asterisk or hash
        e[x++] +                  // … based pixel opacity
        e[x++]
      ] || (S = (                 // otherwise use the original code
        "eval(z='" +              // inception of missing "eval" statement
          z
            .split(B = "\\")      // escape \ with \\
            .join(B + B)

            .split(Q = "'")       // escape ' with \'
            .join(B + Q) +

          Q +                     // add missing ')

          ")////////"             // add extra chars to fill mapping
        )[
          x / 2 +                 // get character at current position
          61 * y-1
        ]

      ).fontcolor(                // colorize outpu
        /\w/.test(S) &&           // test for [0-9A-Z]
        "#03B"                    // render blue
                                  // otherwise pink (default)
      );

    document.body.innerHTML =     // render output
      p +=                        // append new line
      B +                         // add backspace
      "\n";                       // add new line
  }

  setTimeout(z)                   // render animation on next frame
}
z()
aemkei
fuente
5
Tenga en cuenta que también se explica en este video youtube.com/watch?v=RTxtiLp1C8Y
Benjamin Gruenbaum
21

Aquí hay otra versión desofuscada manualmente, moviendo toda la inicialización fuera de expresión en declaraciones propias:

z='p="<"+"pre>"/* ,.oq#+     ,._, */;for(y in n="zw24l6k\
4e3t4jnt4qj24xh2 x/* =<,m#F^    A W###q. */42kty24wrt413n243n\
9h243pdxt41csb yz/* #K       q##H######Am */43iyb6k43pk7243nm\
r24".split(4)){/* dP      cpq#q##########b, */for(a in t=pars\
eInt(n[y],36)+/*         p##@###YG=[#######y */(e=x=r=[]))for\
(r=!r,i=0;t[a/*         d#qg `*PWo##q#######D */]>i;i+=.05)wi\
th(Math)x-= /*        aem1k.com Q###KWR#### W[ */.05,0>cos(o=\
new Date/1e3/*      .Q#########Md#.###OP  A@ , */+x/PI)&&(e[~\
~(32*sin(o)*/* ,    (W#####Xx######.P^     T % */sin(.5+y/7))\
+60] =-~ r);/* #y    `^TqW####P###BP           */for(x=0;122>\
x;)p+="   *#"/* b.        OQ####x#K           */[e[x++]+e[x++\
]]||(S=("eval"/* l         `X#####D  ,       */+"(z=\'"+z.spl\
it(B = "\\\\")./*           G####B" #       */join(B+B).split\
(Q="\'").join(B+Q/*          VQBP`        */)+Q+")//m1k")[x/2\
+61*y-1]).fontcolor/*         TP         */(/\\w/.test(S)&&"#\
03B");document.body.innerHTML=p+=B+"\\n"}setTimeout(z)';

p = "<" + "pre>";
n = ["zw2", "l6k", "e3t", "jnt", "qj2", "xh2 x/* =<,m#F^    A W###q. */", "2kty2", "wrt", "13n2", "3n9h2", "3pdxt", "1csb yz/* #K       q##H######Am */", "3iyb6k", "3pk72", "3nmr2", ""]
for (y in n) {
    e = [];
    x = 0;
    r = true;
    t = parseInt(n[y], 36) + "";
    for (a in t) {
        r = !r
        for (i = 0; i < t[a]; i += 0.05) {
             x -= 0.05;
             o = new Date / 1e3 + x / Math.PI
             if (Math.cos(o) < 0)
                 e[~~(32 * Math.sin(o) * Math.sin(0.5 + y / 7)) + 60] = -~r;
        }
    for (x = 0; x < 122;) {
        S = "eval" + "(z='" + z.split(B = "\\").join(B + B).split(Q = "'").join(B + Q) + Q + ")//m1k"
        p += "   *#"[e[x++] + e[x++]] || S[x/2+61*y-1]).fontcolor(/\w/.test(S[x/2+61*y-1]) && "#03B");
    }
    p += B + "\n";
    document.body.innerHTML = p;
}
setTimeout(z)

Esto es lo que sucede:

  • zes una cadena de varias líneas que contiene todo el código. Es evaled.
  • Al final del código, zse pasa a setTimeout. Funciona como requestAnimationFramey en evalconjunto, evaluándolo en un intervalo al mayor ritmo posible.
  • El código en sí se inicializa p, el búfer de cadena en el que se agregará el HTML y n, una matriz de números codificados en base 36 (unidos en una cadena por "4", los comentarios son basura irrelevante que no es considerada por parseInt).
  • cada número ncodifica una línea ( n.length == 16). Ahora está enumerado .
  • Se inicializa un montón de variables, algunas disfrazadas como el eliteral de matriz, pero luego se convierten en números ( x) o booleanos ( r) o cadenas ( t) cuando se usan.
  • Cada dígito del número tse enumera, invirtiendo el booleano en rcada turno. Para diferentes ángulos x, y dependiendo de la hora actual new Date / 1000 (para que dé una animación), la matriz ese llena usando algunos operadores bit a bit , con 1when res falso 2ys when res verdadero en ese momento.
  • Luego, un bucle itera las 61 columnas de la imagen, desde x=0hasta 122 en pasos dobles, agregando caracteres individuales a p.
  • Bal ser la barra invertida, la cadena Sse construye a partir de la cadena de código zescapando de las barras invertidas y los apóstrofos, para obtener una representación precisa de lo que se veía en la fuente.
  • Cada dos números consecutivos de ese agregan y se usan para acceder a un personaje de " *#", para construir la imagen animada. Si uno de los índices no está definido, el NaNíndice se resuelve en un carácter indefinido y en su lugar Sse toma el carácter respectivo de la cadena (consulte la fórmula x/2+61*y-1). Si ese carácter debe ser un carácter de palabra , se colorea de manera diferente utilizando el fontcolormétodo String .
  • Después de cada línea, se agregan el retroceso final y un salto de línea p, y la cadena HTML se asigna al cuerpo del documento.

¿Cómo podría reescribirse el mismo efecto para un ejemplo mínimo?

Aquí hay otro ejemplo:

setInterval(z='s=("setInterval(z=\'"+\
z.replace(/[\\\\\']/g,"\\\\$&")+"\')"\
).match(/.{1,37}/g).join("\\\\\\n");d\
ocument.body.innerHTML=\"<\\pre>"+s.s\
lice(0, 175)+String( + new Date()).fo\
ntcolor("red")+s.slice(188)')

( demostración en jsfiddle.net )

Tiene todas las cosas relevantes que necesita para este tipo de animación:

  • setIntervaly Datepara la animacion
  • Una reconstrucción de su propio código ( como quine ), aquí:

    s = ( "setInterval(z='" // the outer invokation
          + z.replace(/[\\\']/g,"\\$&") // the escaped version
        + "\')" ) // the end of the assignment
        .match(/.{1,37}/g).join("\\\n"); // chunked into lines
    
  • La salida vía document.body.innerHTMLy un <pre>elemento

  • Reemplazo de algunas partes del código con la cadena animada
Bergi
fuente
2
tengo que admitir, gran respuesta!
rafaelcastrocouto
5

Se evalúa una cadena con todo el código y un tiempo de espera hace el bucle; La cadena se almacena en una variable nombrada zy en el medio del código, entre comentarios /*y */hay un "Arte ASCII de la Tierra". El código analiza los comentarios y cambia el contenido del documento, manteniendo el js y actualizando el art. A continuación se muestra solo el código cortado:

  p="<pre>";
  for(y in n="zw24l6k4e3t4jnt4qj24xh2 x42kty24wrt413n243n9h243pdxt41csb yz43iyb6k43pk7243nmr24".split(4)){ 
    for(a in t = parseInt(n[y],36)+(e=x=r=[]))
      for(r=!r,i=0;t[a]>i;i+=.05)
        with(Math) x-= .05,0>cos(o=new Date/1e3+x/PI)&&(e[~~(32*sin(o)*sin(.5+y/7))+60] =-~ r);
          for(x=0;122>x;) p += "   *#"[e[x++]+e[x++\]] ||
              (S=("eval"+"(z=\'"+z.split(B = "\\\\").join(B+B).split(Q="\'").join(B+Q)+Q+")//m1k")[x/2+61*y-1]).fontcolor(/\\w/.test(S)&&"#\03B");
    p += B+"\\n"
    document.body.innerHTML= p
  }
rafaelcastrocouto
fuente
6
De todos modos, increíble cómo alrededor del Ecuador el gráfico tiene más efectos ... increíble. +1 BTW
Roko C. Buljan