injectso - Inject shared libraries into running processes

Shaun Clowes (injectso@securereality.com.au)

Contents

  1. Introduction to injectso
  2. Limitations of injectso
  3. Ports of injectso
  4. The original injectso presentation
  5. What can injectso do?
  6. Compiling
  7. First looks
  8. Using injectso
  9. Intercept Routines
  10. Command Line
  11. Feedback

Introduction to injectso

This is the second public release of injectso, a tool to inject shared libraries into running processes (and assist the injected libraries in modifying the target process).

injectso was first presented at the BlackHat Briefings 2001 in Amsterdam, Holland.

Limitations of injectso

This version of injectso should be able to inject 32 bit ELF shared libraries into 32 bit ELF executable processes on Linux (IA32/x86 and Sparc) and Solaris (Sparc). It will NOT work on threaded programs.

injectso is currently in the 'proof of concept' phase. It does work, and I haven't found any processes it kills, but the code certainly needs to be cleaned up a lot and the platform specific code really needs to be abstracted.

Ports of injectso

I would very much like to port injectso to other architectures / platforms, to do that I need people to contribute ports or to get access to machines to develop on. I'd be very grateful to anyone who could give me access to develop on their machines, in particular I'd like to extend Solaris support to include IA32 and would like to support HPUX on PA-RISC

The original injectso presentation

As mentioned earlier, injectso was first presented at the BlackHat Briefings in Amsterdam, Holland 2001. The slides for the presentation can be downloaded from the BlackHat site:

http://www.blackhat.com/presentations/bh-europe-01/shaun-clowes/injectso3.ppt

The presentation talks in detail about InjLib, a very old tool/technique used to inject dlls into windows processes. injectso is to some degree the same thing, but for Unix (though I like to believe it's a little more powerful than bare InjLib).

What can injectso do?

This is a bit of a hard question, given that injectso is in and of itself a rather generic tool. I guess to some degree the answer is 'a lot'. The ability to execute code in the context of another process can be amazingly powerful.

Using injectso with a simple shared library you could:

While injectso is itself rather general in application, it comes with a set of routines that are designed to be linked into shared libraries that will later be injected into other processes. These routines (called the intercept routines) allow the shared library to easily intercept calls to functions in other shared libraries.

Using injectso with a simple shared library linked against the intercept routines you could:

injectso can be used for good and for evil, like everything as generic as it is. As easily as it can be used to protect a running process from the latest exploit it can be used to backdoor a process.

Compiling

Compiling injectso is simple:

   ./configure
   make

This should result in an executable called 'injectso' (the injectso program), an object file called 'intercept.o' (the intercept routines) and a shared library called 'libtest.so' (a sample library that uses the intercept routines).

First looks

To get an idea of how injectso works try injecting the supplied test library into a target process. The test library simply overrides calls to the libc read() function in the target and prints out a message each time it is called.

First in one login session start a process, something simple like 'cat' will do:

   [hello@hello injectso-0.2]$ cat

Then in another login session determine the process id of the target process (e.g 'ps -ef | grep "cat"') and use injectso to inject the test library into that target process id (assuming the process id is 8888). For the first attempt don't use the '-c' flag, this will prevent the injected library from overriding read(), it will simply print a message onto the cat process' standard out once it has been injected:

   [hello@hello injectso-0.2]$ ./injectso -p 8888 ./libtest.so

The output from this sequence looks like the following:

   [hello@hello injectso-0.2]$ cat
   Testing library loaded successfully

Note that cat continues executing exactly as before. Now kill the existing cat and begin a new one (determining its process id as before). This time inject the testing library and use the '-c' flag to cause the intercept_begin routine in the library to be called and function interception to begin:

   [hello@hello injectso-0.2]$ ./injectso -c -p 8888 ./libtest.so

The output from this sequence is quite long since it includes a great deal of debugging information from the intercept routines (since this debugging is explicitly by the testing library). It looks something like this:

   Testing library loaded successfully
   intercept_debug: Received intercept begin with 0x804b008
   intercept_debug: Contents of dynamic section:
   ...
   intercept_debug: Found relocation for function read at 0x8048740 which
   points to 0x804b000
   intercept_debug: Overriding functions nominated for interception:
   intercept_debug: Redirecting read from 0x400c0ac0 to 0x4012d998
   intercept_debug: Leaving intercept_begin

Just as the output indicates the read() function has now been hijacked by the libtest.so library. Try typing something into the cat session, output like this will be generated (more voluminous on Sparc systems):

   ls
   ls
   In read!
   newread called with 0 fd

Read on for information about the process of developing an injection library and using it with injectso.

Using injectso

The first step in using injectso is deciding what it is you're aiming to achieve. Once you've decided that you'll then need to code a shared library to achieve your goal, your library may need to use the intercept.o routines.

The easiest way to explain using injectso to achieve a goal is by going through a detailed example. In this case we'll use injectso to patch a security hole in a daemon at run time. For our example we'll use 'rwhoisd', the Referral Whois Daemon from Network Solutions. Old versions of this daemon (up to 1.5) have a format string vulnerability in the -soa query. Here is what the problem looks like:

   [hello@hello injectso-0.2]$ telnet localhost 4321
   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   %rwhois V-1.5:003fff:00 localhost. (by Network Solutions, Inc. V-1.5.5)
   -soa %p%p%p
   %error 340 Invalid Authority Area: 0xbffff6650x8082f900x1
   quit
   %error 230 No Objects Found
   Connection closed by foreign host.

For those not familiar with format string vulnerabilities, what's happening here is that when the daemon generates the "Invalud Authority Area" it is passing the user's input ("%p%p%p") as the format argument to a format string function (e.g sprintf, printf, syslog). Format strings are used to determine what other parameters there are to the function, by specifying "%p%p%p" we're indicating that there should be 3 pointer arguments following the format string argument when there is really only one, the code doesn't know that and walks the stack printing the values. When used with the "%n" format specifier this sort of vulnerability can be used to execute remote code on the target. As we'll see we can reasonably easily fix this problem by injecting a library.

First lets get a good idea of exactly what the daemon is doing, to do this we'll use 'ltrace'. ltrace is included in most Linux distributions and can be used to trace library function calls in a program (Solaris' sotruss command is mostly equivalent). Here is some selective output from an ltrace session monitoring the rwhoisd daemon while it is being probed like above:

   [hello@hello injectso-0.2]$ ltrace -f -p `pidof rwhoisd`
   fork()                                            = 2160
   [pid 928] close(4)                                = 0
   [pid 928] accept(3, 0xbffff894, 0xbffff87c, 1, 0xbffff904 
   ...
   [pid 2160] vfprintf(0x4015a980, "V-%s:%6.6x:00 %s (by Network Sol"..., 
   0xbffff634) = 68
   [pid 2160] fprintf(0x4015a980, "\n")              = 1
   [pid 2160] fflush(0x4015a980)                     = 0
   ...
   [pid 2160] fgets("-soa %p%p\r\n", 512, 0x4015a8c0) = 0xbffff660
   [pid 2160] signal(14, 0x00000001)                 = 0x0804a710
   ...
   [pid 2160] malloc(5)                              = 0x0808b178
   [pid 2160] strcpy(0x0808b178, "%p%p")             = 0x0808b178
   [pid 2160] calloc(1, 8)                           = 0x0808b188
   [pid 2160] realloc(0x0808b188, 8)                 = 0x0808b188
   [pid 2160] calloc(1, 20)                          = 0x0808b198
   [pid 2160] strcasecmp("a.com", "%p%p")            = 60
   [pid 2160] strcasecmp("10.0.0.0/8", "%p%p")       = 12
   [pid 2160] printf("%%error %s", "340 Invalid Authority Area") = 33
   [pid 2160] printf(": ")                           = 2
   [pid 2160] vfprintf(0x4015a980, "%p%p", 0xbffff588) = 19
   [pid 2160] printf("\n")                           = 1

We can clearly see the fgets() call that is receiving the query into a buffer. The daemon then processes the query, fails to find a matching result then goes to print an error message. To do so it executes two printf() calls to print the "%error 340 Invalid Authority Area: " prefix, it then calls vfprintf passing the query string ("%p%p") in the format parameter. It is this call to vfprintf which introduces the vulnerability.

There are a number of approaches to fixing this vulnerability using injectso (and the intercept routines). What we'll do for this example is intercept calls to fgets() in the daemon and filter "-soa" queries to remove any '%' characters, thus killing any format string attack. Here is the source of the shared library we can use to do exactly that:

      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <unistd.h>
      4 #include <intercept.h>
      5
      6 /* Fixes the -soa format string vulnerability in rwhoisd */
      7
      8 char *(*oldfgets)(char *s, int size, FILE *stream);
      9 char *myfgets(char *s, int size, FILE *stream);
     10
     11 SIntercept tFGetsIntercept =
     12    { "fgets", &myfgets, 0x0, (void **) &oldfgets, 0x0 };
     13 SIntercept *ptInterceptFuncs[] = { &tFGetsIntercept, 0x0 };
     14
     15 char *myfgets(char *s, int size, FILE *stream) {
     16    char *sRc;
     17    char *sFirstWord;
     18    char *sPercent;
     19
     20    intercept_fix_unresolved(&tFGetsIntercept);
     21    sRc = oldfgets(s, size, stream);
     22    intercept_override(&tFGetsIntercept);
     23
     24    if (!sRc)
     25       return(sRc);
     26
     27    /* Is this an SOA request? */
     28    sFirstWord = strchr(sRc, '-');
     29
     30    if (!sFirstWord)
     31       return(sRc);
     32
     33    if (strncmp(sFirstWord, "-soa", 4))
     34       return(sRc);
     35
     36    /* It is, strip any %'s inside */
     37    while (sPercent = strchr(sRc, '%'))
     38       memmove(sPercent, sPercent + 1, strlen(sRc));
     39
     40    return(sRc);
     41 }

Line 8 of the source declares a function pointer that will later be used to be hold the address of the real fgets() function in libc after it has been hijacked by the injected library using the intercept routines (see Intercept Routines for detailed information on the intercept routines).

Lines 11 through 13 declare variables that are used by the intercept routines to determine which routines to intercept and where to redirect them to. In this case the function to be intercepted is "fgets" and it is to be redirected to myfgets and the address of the original fgets is to be stored in oldfgets.

Lines 15 through 41 contain the new fgets function. The first thing the replacement function does is call the original function (on lines 20 through 22) to actually cause the string to be read in from the network as normal. It then determines if the command specified is an "-soa" query (on lines 27 through 34), if it isn't the function simply returns without doing anything. If the command is an "-soa" query the function removes all '%' characters from the query string (on lines 36 to 38).

So this library should be able to fix the rwhoisd vulnerability, first we compile it:

   [hello@hello injectso-0.2]$ gcc -g -shared -nostdlib -I. -o fixrwhoisd.so 
   fixrwhoisd.c intercept.o

Note the inclusion of "." in the include path (with -I.) so that the compiler can find intercept.h. Also note the inclusion of the intercept.o object file (containing the intercept routines) in the in the compile line.

Now the library simply needs to be injected into the daemon. To do so we just need to run injectso, specifying the process id of the target and the library to be injected. We also need to specify the "-c" command line option which will cause the "intercept_begin" function linked into our library in the intercept.o routines to be called as soon the library has been injected. The "intercept_begin" routine will process the list of functions to be intercepted and redirect them as we specified. The command looks like this:

   [hello@hello injectso-0.2]$ ./injectso -c -p `pidof rwhoisd` ./fixrwhoisd.so

The daemon should now be protected from this particular vulnerability, here is output from the probe now:

   [hello@hello injectso-0.2]$ telnet localhost 4321
   Trying 127.0.0.1...
   Connected to localhost.
   Escape character is '^]'.
   %rwhois V-1.5:003fff:00 localhost. (by Network Solutions, Inc. V-1.5.5)
   -soa %p%p%p
   %error 340 Invalid Authority Area: ppp
   -soa %p%p%p%p
   %error 340 Invalid Authority Area: pppp
   quit
   %error 230 No Objects Found
   Connection closed by foreign host.

Intercept Routines

injectso comes with some utility routines that can be used by shared libraries to intercept calls to other shared library functions. The intention is that the intercept routines will be linked into libraries that will later be injected into processes using injectso. Once injected the libraries can then easily hijack calls from the program to routines in other shared libraries. For example, using the intercept routines it is trivial to construct a shared library that can intercept all calls from a target program to libc's read() function.

When injectso is compiled the intercept routines are compiled into an object file called intercept.o. Libraries that wish to use the routines should link in the object file and include the header file, intercept.h. For example:

   [hello@hello injectso-0.2]$ gcc -g -shared -nostdlib -I. -o lib.so 
   lib.c intercept.o

To use the intercept functionality the library declares a null terminated array of called ptInterceptFuncs. Each pointer is a pointer a structure of type SIntercept. Each one of the structures describes a function that is to be intercepted. The structure looks like the following:

   typedef struct _SIntercept {
      char *sFuncName;
      void *pvNewFunc;
      void *pvRelAddr;
      void **ppvOldAddr;
      int  iFlags;
   } SIntercept;

The meaning of the fields is as follows:

FieldDescription
sFuncName  This field is a pointer to a string containing the name of the function to be intercepted
pvNewFunc  This field is a pointer to the function to which calls to the function of name sFuncName should be redirected
pvRelAddr  Used internally by the intercept routines
ppvOldAddr  This field can contain a pointer to a function pointer that will be set by the intercept routines to indicate the address of the function that has now been intercepted. If the user isn't interested in the old function address this field should be null
iFlags  This field contains flags to modify the behaviour of the intercept routines when intercepting this function. No interesting flags are provided at this stage so this field will always be 0.

As an example, the following SIntercept structure describes intercepting the libc read() function. Calls to read() are to be redirected to myread() and the address of the real read() function is to be stored in the oldread function pointer:

   SIntercept tReadIntercept =
      { "read", &myread, 0x0, (void **) &oldread, 0x0 };

If the library only wished to intercept read() as described above the following ptInterceptFuncs would be used:

   SIntercept *ptInterceptFuncs[] = { &tReadIntercept, 0x0 };

If the intercepting function wishes to call on to the old function it can use the oldread pointer as provided by the intercept routines. However it must wrap calls to the old function with calls to intercept_fix_unresolved() and intercept_override(). These calls are necessary due to the way injectso intercepts functions (for more detail on the exact reasons please read the intercept.c source code). The two functions can either be called with a null parameter (in which case they will perform their function for all routines in the ptInterceptFuncs array) or more efficiently with a pointer to the SIntercept structure for the function in question. For example, a call to oldread should look like the following:

   intercept_fix_unresolved(&tReadIntercept);
   tRc = oldread(fd, buf, count);
   intercept_override(&tReadIntercept);

Note: To begin interception the intercept_begin() routine needs to be called with a pointer to the dynamic segment of the target executable. This is normally achieved using the '-c' option to injectso which can be used to call a routine in the injected shared library passing the dynamic segment address.

Command Line

The syntax used to invoke injectso is as follows:

injectso [option ...] -p pid library [targetprogram]

The parameters that must be provided are as follows:

ParameterDescription
-p pid  Inject the specified library into the process indicated by the process id, pid
library  Inject the library specified by the filename library into the target process
targetprogram  Normally injectso can automatically determine the filename for the program running in the target process but should it be unable to for some reason this parameter must be used to provide the filename

The following command line options are supported:

FlagDescription
-?, -h Show usage information
-v, -vv, -vvv  Output debugging information to stderr, the number of v's indicates the amount of information to output
-c [function name]  After successfully injecting the shared library into the remote process call the specified function (which defaults to "intercept_begin" if not specified), passing the address of the target executable's dynamic segment.
-n  When injectso attaches to the target process it will interrupt any system call that the process was executing (e.g a blocking read(), select()). Normally injectso attempts to restart that system call when it has finished injecting the library. This option forces it NOT to attempt to restart the system call, instead setting it's return value to EINTR
-l  injectso normally uses symbol hash tables in the target to perform quick symbol lookups. This option forces injectso NOT to use the hash tables.

Feedback

I'm very interested in any (constructive) feedback on injectso, feel free to email me at injectso@securereality.com.au