
As imagens são geralmente armazenadas como arquivos binários e o arquivo Javascript é essencialmente um texto simples. Ambos os tipos de arquivos devem seguir suas próprias regras: as imagens têm um formato de arquivo específico que codifica os dados de uma maneira específica. Para que os arquivos Javascript sejam executáveis, eles devem seguir uma sintaxe específica. Eu me perguntei: é possível criar um arquivo de imagem que tenha sintaxe Javascript válida para que possa ser executado?
Antes de continuar lendo, eu recomendo explorar esta caixa de proteção de código com os resultados de meus experimentos:
https://codesandbox.io/s/executable-gif-8yq0j?file=/index.html
Se você quiser visualizar a imagem e explorá-la você mesmo, então Você pode fazer o download aqui:
https://executable-gif.glitch.me/image.gif
Escolha do tipo certo de imagem
Infelizmente, as imagens contêm muitos dados binários que, quando interpretados como Javascript, geram um erro. Então, meu primeiro pensamento foi este: e se eu apenas colocasse todos os dados da imagem em um grande comentário, algo assim:
/*ALL OF THE BINARY IMAGE DATA*/
Este será um arquivo Javascript válido. No entanto, os arquivos de imagem devem começar com uma sequência de bytes específica, um cabeçalho de arquivo específico para o formato da imagem. Por exemplo, os arquivos PNG devem sempre começar com a sequência de bytes 89 50 4E 47 0D 0A 1A 0A . Se a imagem começar com
/*, então o arquivo não é mais um arquivo de imagem.
Este cabeçalho de arquivo me levou à seguinte ideia: e se usarmos essa sequência de bytes como um nome de variável e atribuirmos a ela o valor de uma longa string:
PNG=`ALL OF THE BINARY IMAGE DATA`;
Usamos strings de modelo em vez de strings regulares,
"ou 'porque os dados binários podem conter quebras de linha e as strings de modelo funcionam melhor com eles.
Infelizmente, a maioria das sequências de bytes em cabeçalhos de arquivo de imagem contém caracteres não imprimíveis que não podem ser usados em nomes de variáveis. Mas existe um formato que podemos usar: GIF. O bloco de cabeçalho GIF se parece com 47 49 46 38 39 61 , que é convenientemente convertido para a string ASCII GIF89a - nome de variável absolutamente válido!
Escolhendo o tamanho certo da imagem
Agora que encontramos um formato de imagem que começa com um nome de variável válido, precisamos adicionar o sinal de igual e o crase. Portanto, os próximos quatro bytes do arquivo serão: 3D 09 60 04

Primeiros bytes da imagem
No formato GIF, os quatro bytes após o cabeçalho definem as dimensões da imagem. Precisamos encaixar nelas 3D (sinal de igual) e 60 (crase, abertura de linha). GIF usa ordenação little endian, portanto, o segundo e o quarto caracteres têm um grande impacto no tamanho da imagem. Eles devem ser os menores possíveis para que a imagem não tenha dezenas de milhares de pixels de largura e altura. Portanto, precisamos armazenar bytes 3D grandes e 60 em bytes menos significativos.
O segundo byte da largura da imagem deve ser um espaço em branco válido, pois será um espaço entre o sinal de igual e o início da linha
GIF89a= `...... Vale lembrar também que o código do caractere hexadecimal deve ser o menor possível, caso contrário a imagem ficará enorme.
O menor caractere de espaço em branco é 09 (caractere de tabulação horizontal). Ele nos dá a largura da imagem 3D 09 , que é 2365 em little endian; um pouco mais largo do que eu gostaria, mas ainda assim perfeitamente aceitável.
Para o segundo byte da altura, você pode escolher um valor que forneça uma boa proporção de aspecto. Eu escolhi 04 , o que nos dá uma altura de 60 04 , ou 1120 pixels.
Colocando o script no arquivo
Até agora, nosso GIF executável não faz quase nada. Ele simplesmente atribui uma
GIF89alonga string à variável global . Queremos que algo interessante aconteça! A maioria dos dados dentro do GIF é usada para codificar a imagem, então se tentarmos inserir Javascript lá, a imagem provavelmente ficará muito distorcida. Mas, por algum motivo, o formato GIF contém algo chamado extensão de comentário . Este é um lugar para armazenar metadados que não são interpretados pelo decodificador GIF - o lugar perfeito para nossa lógica Javascript.
Esta extensão de comentário é encontrada logo após a tabela de cores GIF. Como podemos colocar qualquer conteúdo lá, podemos facilmente fechar a linha GIF89a, adicione todo o Javascript e, em seguida, inicie um bloco de comentários de várias linhas para que o resto da imagem não afete o analisador Javascript.
Em última análise, nosso arquivo pode ter a seguinte aparência:
GIF89a= ` BINARY COLOR TABLE DATA ... COMMENT BLOCK:
`;alert("Javascript!");/*
REST OF THE IMAGE */
No entanto, há uma pequena limitação: embora o bloco de comentário em si possa ser de qualquer tamanho, ele consiste em vários subblocos, e o tamanho máximo de cada um deles é 255. Há um byte entre os subblocos, que determina o comprimento do próximo subbloco. Portanto, para caber um script grande ali, ele precisa ser dividido em pequenos fragmentos, mais ou menos assim:
alert('Javascript');/*0x4A*/console.log('another subblock');/*0x1F*/...
Os códigos hexadecimais nos comentários são bytes que determinam o tamanho do próximo subbloco. Eles não são específicos para Javascript, mas são necessários para o formato de arquivo GIF. Para evitar interferir com o resto do código, eles precisam ser colocados nos comentários. Escrevi um pequeno script que processa fragmentos de script e os adiciona ao arquivo de imagem:
https://gist.github.com/SebastianStamm/c2433819cb9e2e5af84df0904aa43cb8
Limpando dados binários
Agora que temos a estrutura básica, precisamos ter certeza de que os dados binários da imagem não bagunçam a sintaxe do código. Conforme mencionado na seção anterior, o arquivo é dividido em três seções: a primeira é a atribuição à variável GIF89a , a segunda é o código Javascript e a terceira é um comentário de várias linhas.
Vamos dar uma olhada na primeira parte sobre como atribuir um valor a uma variável:
GIF89a= ` BINARY DATA `;
Se os dados binários contiverem um caractere
`ou uma combinação de caracteres ${, teremos um problema, pois eles encerrarão a string de padrão ou criarão uma expressão inválida. A correção é muito simples: basta alterar os dados binários! Por exemplo, em vez de caracteres `(hex 60 ), você pode usar caracteres a(hex 61 ). Como esta parte do arquivo contém uma paleta de cores, isso levará a pequenas alterações em algumas cores, por exemplo, para usar uma cor em #286148vez de #286048. É improvável que alguém perceba a diferença.
Lidando com distorção
No final do código Javascript, abrimos um comentário de várias linhas para que os dados binários da imagem não afetem a análise Javascript:
alert("Script done");/*BINARY IMAGE DATA ...
Se os dados da imagem contiverem uma sequência de caracteres
*/, o comentário terminará prematuramente, tornando o arquivo Javascript inválido. Aqui, novamente, podemos alterar manualmente um dos dois caracteres para que eles não encerrem o comentário. No entanto, como agora estamos na seção da imagem codificada, o resultado será uma imagem danificada, por exemplo:

Imagem danificada
Nos piores casos, a imagem pode não ser exibida. Ao escolher cuidadosamente o bit a inverter, consegui minimizar a distorção. Felizmente, houve apenas alguns casos de combinações prejudiciais
*/. Ainda há pequenas distorções na imagem final, por exemplo, na parte inferior da linha "Arquivo Javascript válido", mas no geral estou muito feliz com o resultado.
Finalizando o arquivo
Ficamos com a última operação - conclusão do arquivo. O arquivo deve terminar com 00 3B bytes , então precisamos terminar o comentário mais cedo. Como este é o fim do arquivo e qualquer dano potencial seria sutil, acabei de terminar o comentário do bloco e adicionei um comentário de uma linha para que o fim do arquivo não causasse problemas de análise:
/* BINARY DATA*/// 00 3B
Persuadir o navegador a executar a imagem
Agora, depois de tudo isso, finalmente temos um arquivo que é uma imagem e um arquivo Javascript válido. No entanto, precisamos superar o último obstáculo: se carregarmos uma imagem para o servidor e tentarmos usá-la em uma tag
script, provavelmente obteremos um erro semelhante:
Recusou-se a executar o script de ' http: // localhost: 8080 / image.gif ' porque seu tipo MIME ('imagem / gif') não é executável. [Recusa de executar script de ' http: // localhost: 8080 / image.gif ' porque seu tipo MIME não é executável.]
Ou seja, o navegador diz com razão: "Esta é uma imagem, não vou executá-la!" E na maioria dos casos, isso é bastante apropriado. Mas ainda queremos cumpri-lo. A solução é simplesmente não dizer ao navegador que se trata de uma imagem. Para isso, escrevi um pequeno servidor que fornece uma imagem sem informações de cabeçalho.
Sem as informações do tipo MIME do cabeçalho, o navegador não sabe que é uma imagem e faz exatamente o que é melhor no contexto: renderizar como uma imagem em uma tag
<img>ou executá-lo como Javascript em uma tag <script>.
Mas ... por que isso tudo?
Eu não descobri sozinho ainda. Essa tarefa é um bom aquecimento para a mente, mas se você conseguir pensar em uma situação em que ela possa ser realmente útil, diga-me!
Publicidade
Servidores para desenvolvedores são servidores virtuais de nossa empresa.
Há muito tempo usamos exclusivamente drives de servidor rápido da Intel e não economizamos em hardware - apenas equipamentos de marca e as soluções mais modernas do mercado para prestação de serviços.
