Abusing LD_PRELOAD for fun and profit

Overriding library calls

The ELF format is pretty common across various unix versions, having superseded previous binary formats such as a.out and COFF. Pretty much, today, if you see a unix binary then it’s probably ELF format.

One of features of the ELF format is that the run time linker can be smart about how it resolves dependencies, and this smartness can be tuned. A typical tuning many people know is the LD_LIBRARY_PATH variable, which can be used to add new directories to be searched for the needed libraries.

Another one, the focus of this entry, is LD_PRELOAD. This can be used to force a library to be loaded and this can be used to alter the way the standard libc calls are processed.

This entry may get a little more technical than normal for this blog; I just felt the need to post some code! I’ll present three unusual use cases:

The snowflake

In small companies or self-managed departments we typically have usernames that are easy to use. So my login may be sweh or sweharris or stephen or similar. This is nice for humans, and makes it easy; an ls or a ps will show the username and the human looking at it will know what person is really being referenced. Unfortunately this isn’t scalable (what if you have two people named “Stephen”). Larger companies tend to give people generated names. The login name may be random or refer to the employee ID. Now xyz123ab or x123456 doesn’t really help.

I had to deal with a manager who ran servers that were being migrated from “self-managed” into the corporate management tool. Of course this meant the “fred” account now had to become “z987654” and he was angry. He demanded to be a unique and special snowflake and keep human friendly names. We weren’t going to do this, but it gave me an idea for a joke. I could create a snowflake.so library that could be pre-loaded. It would override the normal getpwuid() call and rewrite the results.

Ultimately, after the Managing Director told him to shut up, he gave up.

Now a full version of the code had configuration files, but here is a simple variant; it will replace the current user’s username with a variable:

/* This routine intercepts getwpuid()
 * It will call the original, and if the called
 * uid == getuid() and $OVERRIDE_USER is set then
 * replaces the username with $OVERRIDE_USER
 *
 * gcc -o getpwuid_modify.so -fPIC -shared getpwuid_modify.c -ldl
 * export LD_PRELOAD=$PWD/getpwuid_modify.so
 * export OVERRIDE_USER="dummy_username"
 * run the app
 * unset LD_PRELOAD
 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <pwd.h>
#include <stdlib.h>
#include <dlfcn.h>

struct passwd *getpwuid(uid_t uid)
{
  static void * (*func)();
  static uid_t my_uid;
  static char *override;
  struct passwd *p;

  if(!func)
  {
    func = (void *(*)()) dlsym(RTLD_NEXT, "getpwuid");
    my_uid = getuid();
    override = getenv("OVERRIDE_USER");
  }

  p=func(uid);
  if (p && uid == my_uid && override)
    p->pw_name=override;

  return(p);
}

What this code does is intercept the getpwuid function. It then looks for the original function and calls that, then manipulates the result.

To the calling program we just get a different string:

$ gcc -o getpwuid_modify.so -fPIC -shared getpwuid_modify.c -ldl
$ export LD_PRELOAD=$PWD/getpwuid_modify.so
$ whoami
sweh
$ OVERRIDE_USER=root whoami
root

Let’s hope there’s no programs that test the result of whoami to determine what functions are allowed!

Shifting time

Recently, on twitter, a friend asked if there was a good way of setting his computer clock 5 minutes fast. I guess he’s one of these people who needs his alarm to start 5 minutes early. Now you don’t want to play with the system clock because NTP would fight you. The normal way would be to create your own unique tzdata entry (easily based off your local timezone). This will work for most people because their TZ rules don’t change often.

Or you can cheat and override the library calls :-)

/* This routine intercepts time() and clock_gettime() adds 300 seconds.
 *
 * gcc -o time_modify.so -fPIC -shared time_modify.c -ldl
 * export LD_PRELOAD=$PWD/time_modify.so
 * run the app
 * unset LD_PRELOAD
 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <time.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>

time_t time(time_t *tloc)
{
  static time_t (*func)();
  time_t t;

  if(!func)
  {
    func = (time_t (*)()) dlsym(RTLD_NEXT, "time");
  }

  t=func(tloc) + 300;
  if (tloc) *tloc=t;

  return(t);
}

int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
  static int i;
  static int (*func)();

  if(!func)
  {
    func = (int (*)()) dlsym(RTLD_NEXT, "clock_gettime");
  }

  i=func(clk_id,tp);
  tp->tv_sec += 300;

  return(i);
}

We can see the code logic is very very similar and that multiple functions can be overridden.

$ date ; LD_PRELOAD=$PWD/time_modify.so date
Sun Mar  5 18:48:05 EST 2017
Sun Mar  5 18:53:05 EST 2017

I think he eventually went with timezone creation; it’s pretty easy for America/New_York :-)

Overcoming license restrictions

This one was big in the 90s. Sun SPARC servers had an NVRAM chip, and each host had a unique hostid value. Some licensed software had a license file that was based on this hostid. If you tried to run it on another machine then it’d fail to start.

A number of solutions were available for this, including reprogramming the NVRAM, but a common one was to use LD_PRELOAD.

In this case the code is a lot simpler; we don’t need to call out to the original routine or do anything conditional; just return the number we want. (the compile sequence is different here; it’s for Solaris)

long gethostid(void) {
 return 0x12345678;
}

$ gcc -fPIC -c newhostid.c
$ gcc -shared -o newhostid.so newhostid.o
$ export LD_PRELOAD=/tmp/newhostid.so
$ /usr/bin/hostid
12345678

Of course this was never used to allow license keys to be reused on multiple servers at the same time. Oh no, that would be wrong…

Summary

LD_PRELOAD isn’t really something I’d recommend for production quality code. I can see it being useful during development (“I want to test this new function without rebuilding a whole library”) and, as can be seen, it can be used to circumvent some forms of protection. But I wouldn’t consider it supportable!

And, no, you can’t use it against setuid programs; the linker ignores these variables in this case. You can’t make su give you a root shell by overriding library calls :-)

 
comments powered by Disqus