A picture that is also a Javascript code



Images are usually stored as binary files, and the Javascript file is essentially plain text. Both types of files must follow their own rules: images have a specific file format that encodes data in a specific way. In order for Javascript files to be executable, they must follow a specific syntax. I wondered: is it possible to create an image file that is also valid Javascript syntax so that it can be executed?



Before you continue reading, I highly recommend exploring this code sandbox with the results of my experiments:



https://codesandbox.io/s/executable-gif-8yq0j?file=/index.html



If you would like to view the image and explore it yourself, then you can download it from here:



https://executable-gif.glitch.me/image.gif



Choosing the right picture type



Unfortunately, images contain a lot of binary data that, when interpreted as Javascript, will throw an error. So my first thought was this: what if I just put all the image data in a big comment, something like this:



/*ALL OF THE BINARY IMAGE DATA*/


This will be a valid Javascript file. However, image files must begin with a specific sequence of bytes, a file header specific to the image format. For example, PNG files must always start with the byte sequence 89 50 4E 47 0D 0A 1A 0A . If the image starts with /*, then the file is no longer an image file.



This file header led me to the following idea: what if we use this sequence of bytes as a variable name and assign it the value of a long string:



PNG=`ALL OF THE BINARY IMAGE DATA`;


We use template strings instead of regular strings, "or 'because binary data can contain line breaks, and template strings do better with them.



Unfortunately, most of the byte sequences in image file headers contain non-printable characters that cannot be used in variable names. But there is one format that we can use: GIF. The GIF header block looks like 47 49 46 38 39 61 , which is conveniently converted to ASCII string GIF89a - absolutely valid variable name!



Choosing the right image size



Now that we have found an image format that starts with a valid variable name, we need to add the equals sign and the backtick. Therefore, the next four bytes of the file will be: 3D 09 60 04





First Bytes of the Image



In GIF format, the four bytes after the header define the dimensions of the image. We need to fit in them 3D (equal sign) and 60 (backtick, opening a line). GIF uses little endian ordering, so the second and fourth characters have a huge impact on image sizes. They should be as small as possible so that the image is not tens of thousands of pixels wide and high. Hence, we need to store large 3D bytes and 60 in least significant bytes.



The second byte of the image width must be a valid whitespace, because it will be a space between the equal sign and the beginning of the lineGIF89a= `...... It is also worth remembering that the hexadecimal character code should be as small as possible, otherwise the image will be huge.



The smallest whitespace character is 09 (horizontal tab character). It gives us the width of the 3D 09 image , which is 2365 in little endian; a little wider than I would like, but still perfectly acceptable.



For the second height byte, you can choose a value that gives a good aspect ratio. I chose 04 , which gives us a height of 60 04 , or 1120 pixels.



Putting the script in the file



So far, our executable GIF does almost nothing. It simply assigns a GIF89along string to the global variable . We want something interesting to happen! Most of the data inside the GIF is used to encode the image, so if we try to insert Javascript there, the image is likely to be heavily distorted. But for some reason, the GIF format contains something called the Comment Extension . This is a place to store metadata that is not interpreted by the GIF decoder - the perfect place for our Javascript logic.



This comment extension is found right after the GIF color chart. Since we can put any content there, we can easily close the GIF89a line, add all the Javascript and then start a multi-line comment block so that the rest of the image doesn't affect the Javascript parser.



Ultimately, our file may look like this:



GIF89a= ` BINARY COLOR TABLE DATA ... COMMENT BLOCK:

`;alert("Javascript!");/*

REST OF THE IMAGE */


However, there is a small limitation: although the comment block itself can be of any size, it consists of several subblocks, and the maximum size of each of them is 255. There is one byte between the subblocks, which determines the length of the next subblock. Therefore, in order to fit a large script there, it must be divided into small fragments, something like this:



alert('Javascript');/*0x4A*/console.log('another subblock');/*0x1F*/...


The hexadecimal codes in the comments are bytes that determine the size of the next subblock. They are not specific to Javascript, but are required for the GIF file format. To avoid interfering with the rest of the code, they need to be placed in comments. I wrote a small script that processes script fragments and adds them to the image file:



https://gist.github.com/SebastianStamm/c2433819cb9e2e5af84df0904aa43cb8



Cleaning up binary data



Now that we have the basic structure, we need to make sure that the binary image data doesn't mess up the syntax of the code. As mentioned in the previous section, the file is divided into three sections: the first is the assignment to the GIF89a variable , the second is the Javascript code, and the third is a multi-line comment.



Let's take a look at the first part about assigning a value to a variable:



GIF89a= ` BINARY DATA `;


If the binary data contains a character `or a combination of characters ${, then we have a problem, because they will either terminate the pattern string, or create an invalid expression. The fix is ​​pretty simple: just change the binary data! For example, instead of character `(hex 60 ), you can use character a(hex 61 ). Since this part of the file contains a palette of colors, this will lead to minor changes in some colors, for example, to use a color #286148instead of #286048. It is unlikely that anyone will notice the difference.



Dealing with distortion



At the end of the Javascript code, we have opened a multi-line comment so that the binary image data does not affect the Javascript parsing:



alert("Script done");/*BINARY IMAGE DATA ...


If the image data contains a sequence of characters */, the comment will end prematurely, rendering the Javascript file invalid. Here again, we can manually change one of the two characters so that they do not end the comment. However, since we are now in the section of the encoded image, the result will be a damaged image, for example this:





Damaged image



In the worst cases, the image may not be displayed at all. By carefully choosing the bit to invert, I was able to minimize distortion. Fortunately, there were only a few cases of damaging combinations */. There are still some slight distortions in the finished image, for example, at the bottom of the "Valid Javascript File" line, but overall I'm quite happy with the result.



Finishing the file



We are left with the last operation - file completion. The file must end with 00 3B bytes , so we need to end the comment early. Since this is the end of the file and any potential damage would be subtle, I just ended the block comment and added a one-line comment so that the end of the file wouldn't cause parsing problems:



/* BINARY DATA*/// 00 3B


Persuading the browser to execute the image



Now, after all this, we finally have a file that is both an image and a valid Javascript file. However, we need to overcome the last obstacle: if we upload an image to the server and try to use it in a tag script, then we will most likely get a similar error:



Refused to execute script from ' http: // localhost: 8080 / image.gif ' because its MIME type ('image / gif') is not executable. [Abandoning the execution of the script from ' http: // localhost: 8080 / image.gif ' because its MIME type is not executable.]


That is, the browser rightly says: "This is an image, I will not execute it!" And in most cases, this is quite appropriate. But we still want to fulfill it. The solution is to simply not tell the browser that this is an image. For this, I wrote a small server that serves an image without header information.



Without the MIME-type information from the header, the browser doesn't know it's an image and does exactly what is best in context: render it as an image in a tag, <img>or execute it as Javascript in a tag <script>.



But ... why is this all?



I haven't figured it out myself yet. Such a task is a good warm-up for the mind, but if you can think of a situation in which it can really be useful, then let me know!






Advertising



Servers for developers are about virtual servers from our company.

For a long time we have been using exclusively fast server drives from Intel and do not save on hardware - only branded equipment and the most modern solutions on the market for providing services.






All Articles