Como funciona esse JavaScript ofuscado?

93

Como funciona o seguinte JavaScript?

Eu entendo que é um código reduzido. Tentei desofuscá-lo um pouco, mas não consigo ter um conceito claro de como ele atinge esse efeito. Posso ver que ele está usando Strings para algum tipo de iteração, uso do objeto Date, manipulação de strings estranhas, funções matemáticas e, em seguida, o código se imprime.

Como o mesmo efeito poderia ser reescrito com um exemplo 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

Alexandre
fonte
8
Animação legal ... pode acabar usando isso em algum lugar, na verdade!
tymeJV
7
Ah legal. Não percebi o violino.
ThiefMaster
37
Este é chamado de Quine, e este é um dos mais fantásticos Quine que já vi. en.wikipedia.org/wiki/Quine_(computing)
David Souther
9
@Roko C. Buljan acho que esta é a página dele: aem1k.com
Alexander
5
Parece que o autor agora colocou uma versão anotada no GitHub.
Der Hochstapler de

Respostas:

67

Prefácio : Eu embelezei e anotei o código extensivamente em http://jsfiddle.net/WZXYr/2/

Considere a camada mais externa:

eval(z = '...');

Uma string de código é armazenada na variável z . O operador de atribuição retorna o valor atribuído, então a string de código também é passada como um argumento para eval.

A string de código z é executada dentro de eval. O código é extremamente obtuso, mesmo quando limpo, mas parece:

  1. Analisa uma string de números de base 36, delineada pelo caractere 4.
  2. Preencher um mapa de valores, utilizando as variáveis globais e, xey para manter o estado do mapa. O estado do mapa é, em parte, uma função do segundo atual no relógio de parede ( new Date / 1e3).
  3. Usando os valores do mapa, o código gera uma string de saída, p
    • o código usa p += " *#"[index]para decidir se deve usar um espaço, asterisco ou marca de hash, onde indexestá realmente e[x++] + e[x++](como dito acima, ee xsão responsáveis ​​pelo estado do mapa)
    • se o índice for maior do que o comprimento de " *#", haverá um código substituto que preencherá a saída pcom caracteres de z. Os personagens internos são preenchidos com personagens de animação, enquanto os personagens externos são retirados z.

No final do código, há uma chamada para setTimeout(z), que avalia de forma assíncrona a string do código z. Essa chamada repetida de zpermite que o código faça um loop.

Exemplo simples:

Aqui está uma versão super simples ( 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. O forloop adiciona cada caractere à string de saída p(a string tem 172 caracteres):

    for(i=0;i<172;++i)
  2. A condicional interna decide se estamos em um personagem entre as posições 62 a 67, que são os personagens animados:

    if(i > 62 && i < 67)
  3. Se estivermos, imprimamos !---, deslocados com base no décimo do segundo valor do relógio de parede. Isso fornece o efeito de animação.

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

    (Toda a maldade ao redor new Date está realmente lá apenas para transformar um valor de data em um número entre 0 e 3.)

  4. Caso contrário, se não estivermos em um personagem animado, imprima o icaractere de índice da string definida por

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

    Ou seja, a string de código zcercada por eval('e ').

  5. Finalmente, imprima a string e use setTimeoutpara enfileirar outra execução de z:

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

Observe que meu resultado final não está certo - eu não considerei as barras invertidas no final - mas ele ainda deve dar uma boa ideia de como a técnica funciona em geral.

Apsillers
fonte
8
Observe este github.com/aemkei/world/blob/master/annotated.js - a versão anotada do próprio autor no GitHub.
Benjamin Gruenbaum
36

Aqui está a fonte anotada. Ps: eu sou o 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
fonte
5
Observe que também é explicado neste vídeo youtube.com/watch?v=RTxtiLp1C8Y
Benjamin Gruenbaum
21

Aqui está outra versão desofuscada manualmente, movendo todas as inicializações da expressão para as próprias instruções:

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)

Aqui está o que acontece:

  • zé uma string de várias linhas que contém todo o código. É evaled.
  • No final do código, zé passado para setTimeout. Funciona como requestAnimationFrameeeval igual junto, avaliando-o em um intervalo na maior taxa possível.
  • O próprio código é inicializado p, o buffer de string no qual o HTML será anexado e numa matriz de números codificados em base 36 (unidos em uma string por "4", os comentários sendo lixo irrelevante que não é considerado por parseInt).
  • cada número em ncodifica uma linha ( n.length == 16). Agora está enumerado .
  • Um monte de variáveis ​​é inicializado, alguns disfarçados como o eliteral array, mas eles são convertidos em números ( x) ou booleanos ( r) ou strings ( t) quando usados.
  • Cada dígito do número té enumerado, invertendo o booleano a rcada volta. Para ângulos diferentes x, e dependendo da hora atual new Date / 1000 (de modo que dê uma animação), o array eé preenchido usando alguns operadores bit a bit - com 1quando ré falso e 2s quando ré verdadeiro naquele momento.
  • Em seguida, um loop itera as 61 colunas da imagem, de x=0até 122 em etapas duplas, acrescentando caracteres únicos a p.
  • Bsendo a barra invertida, a string Sé construída a partir da string do código zescapando-se de barras invertidas e apóstrofos, para obter uma representação precisa do que parecia na origem.
  • Cada dois números consecutivos de esão adicionados e usados ​​para acessar um personagem de " *#", para construir a imagem animada. Se um dos índices não for definido, o NaNíndice é resolvido para um caractere indefinido e, em vez disso, o respectivo caractere da Sstring é obtido (verifique a fórmula x/2+61*y-1). Se esse caractere for um caractere de palavra , ele será colorido de forma diferente usando o fontcolormétodo String .
  • Após cada linha, o backspace final e uma quebra de linha são adicionados pe a string HTML é atribuída ao corpo do documento.

Como o mesmo efeito poderia ser reescrito para um exemplo mínimo?

Aqui está outro exemplo:

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)')

( demonstração em jsfiddle.net )

Ele tem todas as coisas relevantes de que você precisa para este tipo de animação:

  • setIntervale Datepara a animação
  • Uma reconstrução de seu próprio código ( semelhante a quine ), aqui:

    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
  • A saída via document.body.innerHTMLe um <pre>elemento

  • Substituindo algumas partes do código pela string animada
Bergi
fonte
2
tenho que admitir, ótima resposta!
rafaelcastrocouto de
5

Uma string com todo o código é avaliada e um tempo limite faz o loop; A string é armazenada em uma variável chamada ze no meio do código, entre comentários /*e */há uma "Earth ASCII Art". O código analisa os comentários e altera o conteúdo do documento, mantendo o js e atualizando a arte. Abaixo está apenas o código dividido:

  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
fonte
6
De qualquer forma, incrível como ao redor do Equador o gráfico tem mais giro ... incrível. +1 BTW
Roko C. Buljan