Performing a MacGyver to Call Anyplace Home
In this tiny article, I will show you a workaround that can be helpful in situations when you need to run some types of applications in machines that cannot be updated and also are not able to build any kind of software natively. Maybe the software is too new, maybe the system is too old, and vice versa. With this approach, you are be able in most cases to execute a self-contained binary package in several systems having different versions of libraries, without any updating necessity.
Introduction
Sometimes it is necessary to run an application in several different versions/distributions of an operating system, especially Linux. The problem with this issue is that the compiled program cannot be successfully executed in all machines because the libraries will vary from one OS version to another.
Updating in practice tends to be a tedious bureaucratic task for some large production environments. You can't always just run an updating task. In most cases, you need to plan it in advance, ask/inform a couple of departments, convince the customer, and so on...
What About Carrying Your Home on Your Shoulders?
This is not always the better solution, but in some cases it can be a nice workaround. The idea is basically to compile the software once and scan the related dependency libraries in order to collect all of them. Afterwards, what you should do is distribute the binary and the libraries together.
You need to recompile and some care about this recompilation must be observed.
How Can I Find All Dependencies of Software?
You should use the environment variable called LD_TRACE_LOADED_OBJECTS when calling the application:
$ LD_TRACE_LOADED_OBJECTS=1 ./appWhen executed, a list of all libraries will be shown on the console. These libraries should be copied and distributed together with your software.
Example:
$ LD_TRACE_LOADED_OBJECTS=1 /bin/sh linux-vdso.so.1 (0x00007ffde177c000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f221747d000) /lib64/ld-linux-x86-64.so.2 (0x00007f2217668000)Recompiling the Software
When recompiling the software, you need to pass to the compiler two important options: -rpath and -dynamic-linker.
The -rpath option specifies the directory where the libraries should be searched during the executable loading.
The -dynamic-linker is related to the dynamic linker loader and is actually a linker flag. In GCC the basic command line would be:
The -Wl,<options> is to pass comma-separated <options> on to the linker.
gcc -rpath <directory> -Wl, -dynamic-linker=<directory>The better choice is let -rpath and -dynamic-linker point to the same directory, which should be a directory where you will create and copy all dependencies and binaries and execute the application from there.
Notice that the compilation command line shown is for software written in C or C++. In other languages that generate ELF, this technique will also work, but the method of setting up the -rpath and -dynamic-linker may be different. Try to Google for more information on this.
Automating the Generation of the Package
I have written a shell script (Bash-based) that can scan executables and also libraries. This script collects and compresses the dependencies, making it ready for distribution. It also uses some other minor techniques that I will abstract for the sake of brevity.
The usage of this script is as follows:
$ ./snail.sh --directory <path containing the binaries to be scanned> --output <zip out>Once the -rpath and -dynamic-linker are configured after a recompilation, all that you should do is deploy the zipped dependencies along with the executables and libraries. The important thing here is to extract the dependencies to the directory where the -rpath is pointing. Again: the better choice is to let -rpath and -dynamic-linker point to the same directory.
In the code listing below, you can see the entire shell script. Some users will need to adjust the shebang path according to their systems. You can download the script at: github.com/rafael-santiago/snail.
#!/bin/bash # # Copyright (C) 2015 by Rafael Santiago # # This is free software. You can redistribute it and/or modify under # the terms of the GNU General Public License version 2. # # "snail.sh" # by Rafael Santiago # # Description: a simple script which scans ELF dependencies. # SNAIL_TEMP_DIR=".snail" SNAIL_LD_32="" SNAIL_LD_64="" SHOULD_REMOVE_INTERP=0 INTERP_PATH="" function snail_find_app_deps() { temp_interp=$(setup_interp $1) printf "\t\t@@@ - Inspecting %s's dependencies...\n" $1 for libpath in $(LD_TRACE_LOADED_OBJECTS=1 $1 | grep ".*/" | sed s/.*=\>// | sed s/\(.*//) do filename=$(basename ${libpath}) file_exists=$(ls -1 ${SNAIL_TEMP_DIR}/${filename} 2>/dev/null | wc -l) if [ ${file_exists} -eq 0 ] ; then printf "\t\t\t@@@ - copying: %s... " ${filename} cp ${libpath} ${SNAIL_TEMP_DIR}/ &>/dev/null if [ $? -eq 0 ] ; then printf "copied.\n" else printf "copy error... aborting.\n" fini_snail exit 1 fi else printf "\t\t\t@@@ - already copied: %s.\n" ${filename} fi done if [ ! -z "$temp_interp" ] ; then remove_interp $temp_interp fi printf "\t\t@@@ - done.\n" } function snail_find_so_deps() { temp_interp=$(setup_interp $1) ld_so=${SNAIL_LD_32} if [ $(get_platform_arch) -eq 64 ] ; then ld_so=${SNAIL_LD_64} fi printf "\t\t@@@ - Inspecting %s's dependencies...\n" $1 for libpath in $(LD_TRACE_LOADED_OBJECTS=1 ${ld_so} ./$1 | grep ".*/" | sed s/.*=\>// | sed s/\(.*//) do filename=$(basename ${libpath}) file_exists=$(ls -1 ${SNAIL_TEMP_DIR}/${filename} 2>/dev/null | wc -l) if [ ${file_exists} -eq 0 ] ; then printf "\t\t\t@@@ - copying: %s... " ${filename} cp ${libpath} ${SNAIL_TEMP_DIR}/ &>/dev/null if [ $? -eq 0 ] ; then printf "copied.\n" else printf "copy error... aborting.\n" fini_snail exit 1 fi else printf "\t\t\t@@@ - already copied: %s.\n" ${filename} fi done printf "\t\t@@@ - done.\n" filename=$(basename ${ld_so}) file_exists=$(ls -1 ${SNAIL_TEMP_DIR}/${filename} 2>/dev/null | wc -l) if [ ${file_exists} -eq 0 ] ; then printf "\t\t@@@ - copying: %s... " ${filename} if [ $? -eq 0 ] ; then printf "copied.\n" else printf "copy error... aborting.\n" fini_snail exit 1 fi fi if [ ! -z "$temp_interp" ] ; then remove_interp $temp_interp fi } function is_a_so() { retval=0 if [ $(file $1 | grep ".*: ELF.*shared object," | wc -l) -eq 1 ] ; then retval=1 fi echo ${retval} } function get_elf_arch() { retval=32 if [ $(file $1 | grep ".*: ELF 64-bit" | wc -l) -eq 1 ] ; then retval=64 fi echo ${retval} } LIB_SEARCH_LOCATIONS="/lib /lib32 /lib64" function find_ld_linux32() { SNAIL_LD_32=$(find ${LIB_SEARCH_LOCATIONS} -name "ld-linux.so.2" -executable 2>/dev/null | tail -1) } function find_ld_linux64() { SNAIL_LD_64=$(find ${LIB_SEARCH_LOCATIONS} -name "ld-linux-x86-64.so.2" -executable 2>/dev/null | tail -1) } function get_platform_arch() { retval=32 if [ $(uname -a | grep ".*x86_64" | wc -l) -eq 1 ] ; then retval=64 fi echo ${retval} } function init_snail() { rm -rf ${SNAIL_TEMP_DIR} mkdir ${SNAIL_TEMP_DIR} find_ld_linux32 if [ $(get_platform_arch) -eq 64 ] ; then find_ld_linux64 fi } function setup_interp() { interpreter="" filename=$1 if [ $(file ${filename} | grep ".*: ELF" | wc -l) -eq 1 ] ; then if [ $(is_a_so ${filename}) -ne 1 ] ; then interpreter=$(readelf -l ${filename} | grep "\\[.*:.*\\]" | sed s/.*\\[// | sed s/.*:// | sed s/\\].*//) fi fi if [ ! -z ${interpreter} ] ; then if [ ! -f ${interpreter} ] ; then mkdir -p $(dirname ${interpreter}) if [ $(get_platform_arch) -eq 32 ] ; then cp ${SNAIL_LD_32} ${interpreter} &>/dev/null else cp ${SNAIL_LD_64} ${interpreter} &>/dev/null fi echo ${interpreter} fi fi } function remove_interp() { if [ -f $1 ]; then printf "\t\t\t@@@ - removing temporary interpreter $1\n" rm $1 &>/dev/null rmdir $(dirname $1) &>/dev/null fi } function fini_snail() { rm -rf ${SNAIL_TEMP_DIR} } function zip_deps() { printf "@@@ - Zipping all collected dependencies into %s... " $1 rm $1 &>/dev/null zip -j $1 ${SNAIL_TEMP_DIR}/* &>/dev/null if [ $? -eq 0 ] ; then printf "ok.\n" else printf "zip error... aborting.\n" fini_snail exit 1 fi printf "@@@ - done.\n" } function snail() { printf "@@@@@@@@@@@@@@@@@@@@@\n" printf "@@@ - S n a i l - @@@\n" printf "@@@@@@@@@@@@@@@@@@@@@\n\n" printf "@@@ - Initialising...\n" init_snail $1 printf "@@@ - done.\n\n" printf "@@@ - Now, looking for ELFs in directory %s...\n" $1 for filename in $(find $1 -executable -type f -print) do if [ $(file ${filename} | grep ".*: ELF" | wc -l) -eq 1 ] ; then if [ $(is_a_so ${filename}) -eq 1 ] ; then printf "\t@@@ - Shared object: %s\n" ${filename} snail_find_so_deps ${filename} else printf "\t@@@ - Executable found: %s\n" ${filename} snail_find_app_deps ${filename} fi fi done printf "@@@ - done.\n" zip_deps $2 fini_snail } # main() { directory="" output="" while test -n "$1" do case "$1" in -d | --directory) shift directory="$1" ;; -o | --output) shift output="$1" ;; -h | --help) printf "use: $0 --directory <directory containing your binaries> --output <output file path>\n" exit 1 ;; esac shift done if [ -z ${directory} ] ; then printf "error: --directory option is missing.\n" exit 1 fi if [ -z ${output} ] ; then printf "error: --output option is missing.\n" exit 1 fi snail ${directory} ${output} # }A sample of the script's output is as follows:
$ ./snail.sh --directory test --output test.zip @@@@@@@@@@@@@@@@@@@@@ @@@ - S n a i l - @@@ @@@@@@@@@@@@@@@@@@@@@ @@@ - Initialising... @@@ - done. @@@ - Now, looking for ELFs in directory test... @@@ - Executable found: test/lex @@@ - Inspecting test/lex's dependencies... @@@ - copying: libc.so.7... copied. @@@ - done. @@@ - Executable found: test/more @@@ - Inspecting test/morse's dependencies... @@@ - already copied: libc.so.7. @@@ - done. @@@ - Executable found: test/vi @@@ - Inspecting test/vi's dependencies... @@@ - copying: libutil.so.9... copied. @@@ - copying: libncursesw.so.8... copied. @@@ - already copied: libc.so.7. @@@ - done. @@@ - done. @@@ - Zipping all collected dependencies into test.zip... ok. @@@ - done.As you can see, this output indicates that the subdirectory test was scanned and three executables were found: lex, morse, and vi. The libraries libc.so.morse, and vi. The libraries libc.so.77, libutil.so.9, and libncursesw.so.8 were copied and zipped into test.zip. The ZIP file and the executables (already recompiled) can be successfully executed in other systems with different library versions without any update necessary over there.
Conclusion
This approach is a good way of solving little problems with software deployment, especially in operating systems that come with several distributions - and with each distribution having little changes in some shared libraries (a.k.a. Linux XYZ*). These little changes tend to make the software incompatible from one version to another.
Originally, I wrote this script facing deployment problems in Linux, but it could be extended to other UNIX environments.
I think that the above technique should not be applied in software related to information security, since it can open possibilities of library hooking.
You always should think of this technique as a simple MacGyver workaround, a last resort, "people stop calling me," and so on.
Code snail.sh