How to execute JS like a binary
There are dozens of binary file formats that supported by operating systems that
we use. Binary format is standard that helps operating system to determine how
to execute file: read content, load part of them into a memory, extract
additional data etc.
How operating system determines how it should execute one or the other binary
file? There are several ways to do that: one of that is to look at file extension. For example if you use Windows operating system, you can easily rename any text file to add
.exe-extension and after that it will be looks like executable file, but, for sure, it cannot be executed because it doesn't follow structure of standard executable file for Windows.
Other way to determine that some file can be executed is read several first
bytes of file. This first bytes called magic
number. For example, in UNIX-like operating system, ELF file format, which is being commonly used, has magic number that consists of hexadecimal number
0x7F and followed by
ELF string. More interesting that Java class file and Mach-O file format, which is executable format for MacOS, have the same magic number:
CAFEBABE. Creator of Java, James Gosling, even explained why he chose this
magic number and if it has common with Mach-O executable file format.
If you are using UNIX-like operating system, you can easily check content of any files in your system on byte-level, i.e. using program
hexdump. There is four bytes of executable
hexdump on my MacBook that were represented by hexadecimal numbers:
~ hexdump -n 4 /usr/bin/hexdump 0000000 ca fe ba be 0000004
In linux executable file is executed by kernel and format of executable is determinated using either magic numbers or file extension. Built-in module binfmt is responsible for this process. In linux 5.9 you can find seven file formats that kernel supports out of box. And, surprisingly, one of the format is shebang scripts — scripts, that start with
#! symbol combination. Thus, if you run, using your favorite shell program, script, which has permission to be executed, kernel will decide what to do with your script.
But the most interesting part of this mechanism that you can easily extend it
using custom binary formats. By default, your distribution has mounted folder
~ ls /proc/sys/fs/binfmt_misc register status
.js extension with
node program in the future:
# echo ":nodejs:E::js::/usr/bin/node:" > /proc/sys/fs/binfmt_misc/register
There are list of parameters, which delimited from each other using colons, that we specified in string:
nodejs— name of executable format;
E— specify that we use file extension to determine file format.
Other possible value of this parameter is
M, which means that we should determine
file format by magic number;
js— value of target extension;
/usr/bin/node— path to executable that will be used for executing of file.
After that file
nodejs should appears in the
~ ls /proc/sys/fs/binfmt_misc nodejs register status
~ echo "console.log('Hello, world!')" > index.js ~ chmod +x ./index.js # Add permission to execute file ~ ./index.js Hello, world!