13

Is it possible to write a bash script, which would contain a binary executable program inside?

I mean a script, which would contain a dump of an executable in a textual form, which will be dumped back to an executable by this script when it is executed?

I would love to know a solution, which will work out of the box without a need of installing additional packages. Is it possible?

Thanks!

Katarzyna Gola
  • 133
  • 1
  • 1
  • 6
  • I know it's possible, because I've seen it done in Intel's MKL installer script. I'm not sure *how* it was done, but it is possible. – SethMMorton Aug 23 '13 at 19:53
  • 1
    possible duplicate of [Embed a Executable Binary in a shell script](http://stackoverflow.com/questions/10491704/embed-a-executable-binary-in-a-shell-script) – mschilli Aug 29 '13 at 05:23
  • java 6 jdk's used to have a form of distribution of a single 80+ MB `.sh` file, where the script "ended" with an `exit 0` line and the binary parts followed it. the script extracted the binary parts from itself with a line like `tail ${tail_args} +189 "$0" > $outname`; did a `trap` with `rm`, did a `sum` for checksum, a `chmod` and executed it like `./$outname` – n611x007 Jul 17 '14 at 11:52

9 Answers9

10

i never done something like this before ;) this will compile some c source, create a b.bash script containing the binary (and the original script for simple development)

(a.bash)

#!/bin/bash

if [ "$0" == "b.bash" ];then
  tail -n +$[ `grep -n '^BINARY' $0|cut -d ':' -f 1` + 1 ] $0 | base64 -d > a2.out
  chmod +x a2.out
  ./a2.out
  echo $?
  exit
fi

cat "$0" > b.bash
echo "BINARY" >> b.bash
cat > a.c << EOF
int     main(){
        return 12;
}
EOF
gcc a.c 
base64 a.out >> b.bash

invoke with (a.bash generates b.bash):

bash a.bash ;bash b.bash

i don't know how to evade writing out the binary into a temporary file before execution...

Zoltán Haindrich
  • 1,788
  • 11
  • 20
  • 1
    It is a very good answer - the `base64` is the best solution we can use, since it is default installed on all platforms and it is more compact than `xxd` hex. – Wojciech Danilo Aug 26 '13 at 11:13
  • I needed to add "-w 0" to base64. Otherwise I had linewrapping which will produce error on decoding. May also help if piped to tr. `base64 -w 0 a.out >> b.bash` (or `base64 a.out | tr >> b.bash` i don't tried that https://stackoverflow.com/questions/18410785/bash-script-containing-binary-executable ) – Stretchdude Nov 25 '20 at 11:17
7

Don't reinvent the wheel like several other answers are suggesting, just use the venerable shar command which is precisely doing this by design.

Assuming the file you want to embed in your script is binaryfile, simply run

$ shar binaryfile > binaryfile.shar

and you are set. You have a shell script named binaryfile.shar which when executed will extract binaryfile.

jlliagre
  • 29,783
  • 6
  • 61
  • 72
  • 2
    But you need `sharutils` package installed, which isn’t default. `base64` is always available, as it is part of the GNU coreutils. – erik Feb 07 '14 at 11:39
  • 3
    @erik You assume the OP is using Gnu/Linux but this is not stated. `base64` is no more POSIX than `shar` so isn't either guaranteed to be available on a Unix platform. – jlliagre Feb 07 '14 at 12:10
  • this would seem like the ideal solution except it requires uudecode for binary data, which is not always available. be nice to use `printf` or `echo -e` strings to be extractable with just busybox. – fuzzyTew Mar 12 '22 at 14:51
5

I tried this out and it works. Hex was generated with xxd.

#!/bin/bash

xxd -r >foo <<'EndHere'
0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
0000010: 0200 3e00 0100 0000 e003 4000 0000 0000  ..>.......@.....
0000020: 4000 0000 0000 0000 000a 0000 0000 0000  @...............
0000030: 0000 0000 4000 3800 0800 4000 1e00 1b00  ....@.8...@.....
0000040: 0600 0000 0500 0000 4000 0000 0000 0000  ........@.......
0000050: 4000 4000 0000 0000 4000 4000 0000 0000  @.@.....@.@.....
...
0001960: 6400 5f65 6461 7461 006d 6169 6e00 5f69  d._edata.main._i
0001970: 6e69 7400                                nit.
EndHere
chmod +x foo
./foo
stark
  • 12,615
  • 3
  • 33
  • 50
1

You can convert your binary to text, and then back to binary using uuencode/uudecode.

http://linux.die.net/man/1/uuencode

So you can store your binary data as text in your script and output it to a binary file.

uuencode binaryFile > output.txt

And then put that data into your script and when creating the binary file use uudecode.

dcaswell
  • 3,137
  • 2
  • 26
  • 25
1

So, if I got it right you want to include a binary in your script and execute it on script exit?

Here is a binarymaker script(This does not only create a script that extracts a binary, but merges any your script with any binary):

#!/bin/bash

lineCount=$(wc -l "$1" | cut -f 1 -d ' ') # just get the line count
((lineCount+=2)) # because we are going to append a line

head -n 1 "$1" > "$3" # this is done to ensure that shebang is preserved
echo "trap 'tail -n +$lineCount \$0 > binary; chmod +x binary; ./binary' EXIT" >> "$3"
tail -n +2 "$1" >> "$3"
cat "$2" >> "$3"

exit 0

You should run it like this

./binarymaker myscript mybinary resultscript

If you run resultscript then both myscript and mybinary are going to be executed. You can optionally add a command to rm the binary afterwards.

Also, do not forget to exit at the end of your script because otherwise it will continue and try to parse binary data.

If you're working with another script and not a binary, then it can be executed from pipe like this:

tail -n +$lineCount \$0 | source /dev/stdin

But it is not going to work on real binaries. Also, it doesn't work if your bash version is under 4

Aleks-Daniel Jakimenko-A.
  • 10,335
  • 3
  • 41
  • 39
1

I had to make a installer script to deploy it on a machine that did not even have tar, and I ended embedding busybox (for tar and gunzip) and the tarball to deploy on a shell script. I did it the dd way:

#!/usr/bin/env bash

get_length()
{
    ls -l "$1" | cut -f5 -d' '
}

# First parameter is the number of semicolons to add to second parameter
# NOTE: we do not use an "add_blanks" function directly because it seems bash
# removes duplicated blanks. The workaround is adding other character and
# replacing later using e.g. sed.
add_semicolons()
{
    scratch="$2"
    for n in $(seq 1 $1)
    do
        scratch+=";"
    done
    echo $scratch
}

# Default values
output="installer"
input="deployable.tar.gz"
shell="busybox"

shell_len=$(get_length "$shell")
payload_len=$(get_length "$input")

# START OF INSTALLATION SCRIPT GENERATION.
#
# Note: generated scripts reserves 9 digits for the skip (ibs) offsets.
# When the script is first written, these digits are written as semicolons.
# Later when the lengths are computed, these semicolons are replaced by the
# correct numbers, but the 9-digit length must be preserved by adding blanks
# until 9 digits are filled. Failure to do this will cause the script length
# to vary and offsets would need to be iteratively computed.

# Add shebang
echo "#!/usr/bin/env ash" > "$output"

echo "echo Extracting installer..." >> "$output"
# Add lines to extract binary data and extract payload
echo "mkdir -p /tmp/installer" >> "$output"
echo "dd if=\"$(basename $output)\" ibs=;;;;;;;;; skip=1 | dd ibs=$shell_len count=1 of=/tmp/installer/shell 2>/dev/null" >> "$output"
echo "chmod +x /tmp/installer/shell" >> "$output";
echo "dd if=\"$(basename $output)\" ibs=;;;;;;;;; skip=1 2>/dev/null of=/tmp/installer/payload.tar.gz 2>/dev/null" >> "$output"
# From here on, the binary extraction is completed, do something with extracted files...
# [...]
# Use exit command to avoid the shell script parsing to reach the binary part
echo "echo Done!" >> "$output"
echo "exit 0" >> "$output"

# We reserved 9 blanks for the ibs offsets. Thus fill offsets with blanks up to 9 chars total
script_len=$(get_length "$output")
skip_len=$((script_len + shell_len))

to_add=$((9 - ${#script_len}))
script_len_str=$(add_semicolons $to_add $script_len)
to_add=$((9 - ${#skip_len}))
skip_len_str=$(add_semicolons $to_add $skip_len)

# Add skip values
sed -i "4s/ibs=;;;;;;;;;/ibs=$script_len_str/" "$output"
sed -i "4s/;/ /g" "$output"
sed -i "6s/ibs=;;;;;;;;;/ibs=$skip_len_str/" "$output"
sed -i "6s/;/ /g" "$output"

cat "$shell" >> "$output"
cat "$input" >> "$output"
chmod +x "$output"
Jesús Alonso
  • 301
  • 3
  • 6
1

Here's a one-line approach using hexdump that produces an ascii shell script that will output a binary file on a system having only busybox and nothing else.

hexdump -ve '"printf q" 16/1 "S%o" "q\n"' < INPUT_BINARY_PATH | tr Sq "\\\'" > OUTPUT.sh
sh OUTPUT.sh > OUTPUT_BINARY_PATH

The characters S and q are used to concisely work around symbol limitations in hexdump's format strings. S is translated into a backslash, to represent escaped octal bytes. q is translated into a single quote surrounding each line, so that printf interprets these escapes rather than the shell.

The use of printf could be replaced with echo -ne. The use of octal escaped bytes could be replaced with hexadecimal ones. The use of hexdump could likely be replaced by other similar utilities. More data can be placed on a single script line by increasing the number 16.

fuzzyTew
  • 3,511
  • 29
  • 24
0

Try out makeself

It's 100% open source (GPL v2 license).

  1. https://makeself.io/
  2. https://github.com/megastep/makeself

How I found out about it:

If you download the latest version of Microchip's MPLAB X IDE For microcontrollers, here, for Linux: https://www.microchip.com/en-us/tools-resources/develop/mplab-x-ide#tabs

wget https://ww1.microchip.com/downloads/aemDocuments/documents/DEV/ProductDocuments/SoftwareTools/MPLABX-v6.10-linux-installer.tar

...and extract it and view it with less:

tar -xf MPLABX-v6.10-linux-installer.tar
less MPLABX-v6.10-linux-installer.sh

...you'll see it is a massive 1 GB! shell script which contains this at the top:

#!/bin/sh
# This script was generated using Makeself 2.1.5

CRCsum="108692005"
MD5="bbc1b2d1d3ab9ead5ad3ac936eda9b80"
TMPROOT=${TMPDIR:=/tmp}

label="MPLAB X 'v6.10' installer"
script="./MPLABX-v6.10-linux-installer.run"
scriptargs=""
targetdir="makeself-18675-20230516193821"
filesizes="999203313"
keep=n

#################################################################################
#### Added by Microchip to make these checks run first during installation ######
#################################################################################

check64BitLibraries()
{
...

Notice:

This script was generated using Makeself 2.1.5

So, I recommend trying that out.

See also:

  1. This may also be useful (not sure yet): https://www.xmodulo.com/embed-binary-file-bash-script.html
  2. Google search for "how to store a binary into an sh file"
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
-2

Sure it is!

You can for example dump any binary into a file with:

echo $binary > file

There are many installation scripts which do things like that.

Stefan
  • 157
  • 9
  • 1
    `printf '%s' "$binary" > file` is safer, as `echo` will append an extra newline. How to disable that, as well as treatment of backslash-escaped characters in `$binary`, may differ between implementations of `echo`. – chepner Aug 23 '13 at 19:58
  • 1
    This will fail if `$binary` contains NUL (ASCII 0), HT (ASCII 9), or LF (ASCII 10), or sequences of multiple consecutive SP (ASCII 20), or if it contains `?` or `*` and the `nullglob` option is set. A lot of these can be addressed by writing `"$binary"` instead of `$binary`, but even then it will fail if `$binary` contains NUL. That's a dealbreaker for many (most?) binary files. – ruakh Aug 23 '13 at 20:24
  • @chepner: The question specifies Bash. – ruakh Aug 23 '13 at 20:25
  • @ruakh: `bash` would require `echo -n` at the least, but one also needs to check if `xpg_echo` is set, which affects how the escape characters are processed. It's simplest to just use `printf`. None of which helps with null characters, though. – chepner Aug 23 '13 at 20:44