Bash script containing binary executable
Asked Answered
K

9

14

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!

Kursh answered 23/8, 2013 at 19:51 Comment(3)
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.Morrell
possible duplicate of Embed a Executable Binary in a shell scriptUnlookedfor
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 ./$outnameScion
H
11

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...

Hereld answered 23/8, 2013 at 20:2 Comment(2)
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.Jody
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 #18411285 )Spiritualty
G
11

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.

Gobi answered 24/8, 2013 at 0:9 Comment(3)
But you need sharutils package installed, which isn’t default. base64 is always available, as it is part of the GNU coreutils.Granule
@Granule 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.Gobi
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.Proton
C
4

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  [email protected]...@.....
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
Cumberland answered 23/8, 2013 at 20:34 Comment(2)
+1. base64 is more suited than xxd though. It's way more compact.Amphitrite
Yes, but if you have a hex dump xxd is the way to go.Cumberland
K
2

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"
Karlykarlyn answered 21/1, 2019 at 7:38 Comment(0)
J
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.

Jasper answered 23/8, 2013 at 19:57 Comment(0)
A
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

Alboran answered 23/8, 2013 at 20:49 Comment(0)
P
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.

Proton answered 12/3, 2022 at 15:24 Comment(0)
H
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"
Hammons answered 7/6, 2023 at 20:37 Comment(0)
G
-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.

Grugru answered 23/8, 2013 at 19:53 Comment(4)
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.Corydalis
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.Stinkwood
@chepner: The question specifies Bash.Stinkwood
@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.Corydalis

© 2022 - 2024 — McMap. All rights reserved.