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:

  * Send and receive information over open sockets in that process
  * Read and write to files opened exclusively by that process
  * Close a file descriptor to a socket and redirect the i/o to a file for
    debugging
  * Release resources open in the target that aren't actually needed

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:

  * Intercept all reads into the process, filtering malicious input
  * Intercept routines to provide profiling or debugging information (e.g
    malloc profiling)
  * Snoop on the input and output on another process (a runtime version of
    ttysnoop)

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 *pptInterceptFuncs[] = { &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 pptInterceptFuncs. 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:

Field       Description                                                        
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
pptInterceptFuncs would be used:

   SIntercept *pptInterceptFuncs[] = { &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 pptInterceptFuncs 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:

Parameter      Description                                                     
-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:

Flag                Description                                                
-?, -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

