July 19, 2014

Recovering from removed libgcc_s.so.1 (and missing busybox)

I was experimenting in my arm chroot, and after a gcc upgrade and emerge --depclean --ask that removed the old gcc I got the following error:

# ls -l
ls: error while loading shared libraries: libgcc_s.so.1: cannot open shared object file: No such file or directory

Fortunately the newer working gcc was present, so the steps to make things work again were:

# LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/lib/gcc/armv7a-hardfloat-linux-gnueabi/4.8.2/" gcc-config -l
 * gcc-config: Active gcc profile is invalid!

 [1] armv7a-hardfloat-linux-gnueabi-4.8.2

# LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/lib/gcc/armv7a-hardfloat-linux-gnueabi/4.8.2/" gcc-config 1 
 * Switching native-compiler to armv7a-hardfloat-linux-gnueabi-4.8.2 ...

Actually my first thought was using busybox. The unexpected breakage during a routine gcc upgrade made me do some research in case I can't rely on /bin/busybox being present and working.

I highly recommend the following links for further reading:

It seems useful to re-post the critical parts of the last one and analyze it a bit more. The goal is to transfer busybox binary between systems and set the executable bit.

First, on the working system:

$ alias encode='{ tr -d \\n | sed "s#\\(..\\)#\\\\x\\1#g"; echo; }'
$ alias upload='{ xxd -p | encode | nc -q0 -s -lp 5050; }'
$ upload < /bin/busybox

Then, on the broken one (the IP address needs to be adjusted of course):

# alias decode='while read -ru9 line; do printf "$line"; done'
# alias download='( exec 9<>/dev/tcp/; decode )'
# download > busybox

This gives a non-executable binary though. The way to set it is quite interesting. On the working system:

$ { base64 -d | gzip -d; } > setx.c << EOF
$ gcc -Wall -Wextra -pedantic -nostdlib -Os -fpic -shared setx.c -o setx
$ upload < setx

And then on the broken one:

# ( download > setx; enable -f ./setx setx; setx; )

It's actually the first time I see the "enable" command. What is it?

enable [-a] [-dnps] [-f filename] [name ...]
Enable and disable builtin shell commands.  [...] The -f option means to load the new builtin command name from shared object filename, on systems that support dynamic loading.

Let's look at the actual C source code:

extern int chmod(const char *pathname, unsigned int mode);

int entry(void) {

        return !! chmod("busybox", 0700);
char *desc[] = {0};

struct quick_hack {

        char *name; int (*fn)(void); int on;
        char **long_doc, *short_doc, *other;

} setx_struct = { "setx", entry, 1, desc, "chmod 0700 busybox", 0 };

The name of the struct for builtin named foo is always foo_struct. This doesn't seem to be explicitly documented, but bash source code (builtins/enable.def as of bash-4.2) confirms it:

  /* For each new builtin in the shared object, find it and its describing
     structure.  If this is overwriting an existing builtin, do so, otherwise
     save the loaded struct for creating the new list of builtins. */
  for (replaced = new = 0; list; list = list->next)
      name = list->word->word;

      size = strlen (name);
      struct_name = (char *)xmalloc (size + 8);
      strcpy (struct_name, name);
      strcpy (struct_name + size, "_struct");

      b = (struct builtin *)dlsym (handle, struct_name);

After looking closer at the setx source code, we could analyze encoding and decoding aliases as well.

Interestingly, xxd command is provided by app-editors/vim-core. This is what it does:

xxd  creates  a  hex  dump of a given file or standard input.  It can also convert a hex dump back to its original binary form. Like uuencode(1) and uudecode(1) it allows the transmission of binary data in a `mail-safe' ASCII representation, but  has  the advantage of decoding to standard output.  Moreover, it can be used to perform binary file patching.

-p | -ps | -postscript | -plain
              output in postscript continuous hexdump style. Also known as plain hexdump style.

For more info you may find the following links useful:

After that 'tr -d \\n' removes newlines, and 'sed "s#\\(..\\)#\\\\x\\1#g"' replaces pairs of characters with escape sequences that are understood by printf in decode, which also uses the read builtin:

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
One  line  is read from the standard input, or from the file descriptor fd supplied as an argument to the -u option, and the first word is assigned to the first name, the second word to the second name, and so on,  with  leftover  words  and their intervening separators assigned to the last name.  [...]

              -r     Backslash does not act as an escape character.  The backslash is considered to be part of the line.  In  particular, a backslash-newline pair may not be used as a line continuation.

              -u fd  Read input from file descriptor fd.

The -u 9 option which tells read to use FD 9, corresponds to the redirection in "download" alias, "9<>/dev/tcp/".

With this solution (just to give credit it's taken from an anonymous reddit user, I just went into more details in my post) it's possible to get a working busybox on any system just having a bash session open.

I think the "enable" builtin could also be used to recover from e.g. fork bombs, since running a builtin doesn't require a separate process. That could be an idea for another post...

If you like this post, please use the +1 button or one of the other ways to share. I appreciate your feedback.


Anonymous said...

Nice work :)

Anonymous said...

That's Gentoo bug #433161 for you. A patch for toolchain.eclass that avoids this mess has been posted, but never applied upstream...

Post a Comment