Exploiting The Lexmark PostScript Stack

Aaron Adams (NCC Group)

HITBSecConf2023 - Phuket · Day 1 · Track 1

Overview

In this insightful talk from HITB Phuket, Aaron Adams of NCC Group delves into the intricacies of exploiting the custom PostScript stack implemented in Lexmark printers. Stemming from his team's participation in the Pwn2Own competitions, Adams unveils two critical vulnerabilities – an integer overflow and a type confusion – that allowed for arbitrary code execution on targeted Lexmark devices. The research highlights a significant, often overlooked, attack surface within enterprise environments: the firmware of network-connected printers.

Watch on YouTube

Visual summary for Exploiting The Lexmark PostScript Stack by Aaron Adams
Visual summary for Exploiting The Lexmark PostScript Stack by Aaron Adams

Key moments

  1. 0:00 Introduction to Lexmark printer exploitation
  2. 2:40 Lexmark's custom PostScript stack implementation
  3. 7:00 Understanding PostScript operand stack structure
  4. 17:00 First bug: Index operator integer overflow
  5. 21:00 Exploiting index bug with full RELRO bypass
  6. 26:00 Second bug: Compose font type confusion
  7. 39:50 Crafting a read/write primitive from type confusion
  8. 44:00 Conclusion, future research, and accessibility in security

Exploiting The Lexmark PostScript Stack

Speakers: Aaron Adams, Security Researcher, NCC Group

Conference: HITB Phuket

YouTube: https://www.youtube.com/watch?v=MPEX4TtZqEU

Overview

In this insightful talk from HITB Phuket, Aaron Adams of NCC Group delves into the intricacies of exploiting the custom PostScript stack implemented in Lexmark printers. Stemming from his team's participation in the Pwn2Own competitions, Adams unveils two critical vulnerabilities – an integer overflow and a type confusion – that allowed for arbitrary code execution on targeted Lexmark devices. The research highlights a significant, often overlooked, attack surface within enterprise environments: the firmware of network-connected printers.

The presentation sheds light on Lexmark's unique approach to PostScript interpretation, which, unlike common implementations like Adobe's or Ghostscript, is entirely custom-rolled. This bespoke nature, coupled with a historical lack of public security research, created a fertile ground for discovering complex memory corruption bugs. Adams meticulously details the architecture of Lexmark's pagemaker binary, the structure of its operand stack, and the nuances of PostScript operators, laying the groundwork for understanding the sophisticated exploitation techniques employed during Pwn2Own.

Ultimately, this talk serves as a compelling case study on the vulnerabilities inherent in custom language interpreters and the challenges of securing embedded systems. It demonstrates how seemingly minor flaws in bounds checking and error handling can be chained together to achieve full system compromise, even in the presence of some modern security mitigations. For defenders, it underscores the critical need to extend security scrutiny beyond traditional endpoints to include networked peripherals like printers, which often operate as privileged Linux systems within the corporate network.

Background

▶ Watch: Introduction to Lexmark printer exploitation (0:00)

Lexmark printers, at their core, are 32-bit ARM Linux boxes, making them attractive targets for security researchers. The specific model targeted in Adams's research, the 3224, was a focus during recent Pwn2Own competitions. While models may change, the underlying architecture and the custom PostScript stack remain largely consistent across over 200 Lexmark printer models, implying broad applicability of discovered vulnerabilities. Researchers typically leverage tools like IDA, Ghidra, and Pwntools for analysis. Gaining root access for debugging is often facilitated by public exploits, such as those released by "blasty," who also open-sourced a firmware decrypter, building on prior work by "Clarity."

Prior to the printer's introduction at Pwn2Own, public research into Lexmark's security posture was scarce. However, the competition quickly changed this, with six issues exploited in 2021 and ten more in 2022, five of which resided in the PostScript stack. This surge in findings underscored the rich attack surface presented by the custom implementation. Unlike Adobe's or Artifex's Ghostscript, Lexmark developed its own PostScript interpreter, housed within the pagemaker binary—a network daemon listening on port 9100. This daemon communicates using PJL (Printer Job Language) before switching to PostScript. Interestingly, Lexmark also implemented its own open-source heap algorithm, which, while not directly exploited in this research, remains an area of potential future interest.

Regarding mitigations, the pagemaker binary presented a mixed bag. Crucially, it was initially not Position Independent Executable (PIE), rendering what little ASLR was present largely ineffective. NX (No-Execute) was enabled, preventing code execution from data segments. Read-Only Relocations (RELRO) was partially implemented initially, becoming full just before the 2022 competition, which complicated Global Offset Table (GOT) overwrites. The binary was also sandboxed using systemd, running with reduced UID/GID, restricted access to /var, and limited syscalls. However, these restrictions did not impede the presented exploits, and a local privilege escalation (LPE), like one published by blasty, would still be required to achieve full root access after a sandbox escape.

The PostScript language itself is a page description language, surprisingly Turing-complete, with a 900-page specification. It's stack-based, utilizing an operand stack for arguments and results, an execution stack for function calls, and a dictionary stack for variable lookups. A peculiar feature is its Reverse Polish Notation, where arguments precede operators. On the operand stack, data types (referred to as operands) are represented as 8-byte structures: a 1-byte type, a 1-byte permission (usually read/write), a 2-byte length, and a 4-byte union that can hold a raw value or a pointer. Key operand types include:

  • Integer: Length is zero, value is raw.
  • String: Length is string length (excluding null byte), value is a pointer to the string data. Can be represented with round braces (ASCII) or angle brackets (hex).
  • Name: Similar to a string but used for dictionary keys. It uses an intermediate structure: the operand points to this structure, which then points to the string data. Names are prefixed with / in PostScript code.
  • Array: Length is the number of elements, value is a pointer to a block of operands. Represented with square brackets.
  • Dictionary: Key-value pairs. Represented with double angle brackets. Keys are typically Name operands.

The interpreter also uses global pointers to manage the operand stack: psob (current top of stack), top (maximum stack index), and bottom (minimum stack index). The default stack size is around 1200 entries, growing dynamically. PostScript functions, known as operators, are mostly implemented in C and defined in a global read-only table. Several operators are crucial for understanding the exploits:

  • add: Pops two numbers, adds them, and pushes the result.
  • getinterval: string offset length getinterval reads length bytes from string starting at offset, pushing the substring onto the stack. The original string is popped.
  • dup: Duplicates the top operand on the stack. The new operand points to the same memory as the original.
  • putinterval: string offset new_string putinterval writes new_string into string at offset. It does not push a result onto the stack.
  • index: val1 val2 val3 N index duplicates the operand at index N (relative to the bottom of the stack) and pushes it to the top.

Key Findings

▶ Watch: Understanding PostScript operand stack structure (7:00)

The research uncovered two distinct, high-impact vulnerabilities within Lexmark's custom PostScript interpreter, both leading to arbitrary memory manipulation and ultimately, remote code execution. These findings are particularly significant given the proprietary nature of the interpreter and the historical lack of public security scrutiny.

First, an integer overflow vulnerability was identified in the index operator's bounds checking logic. This flaw allowed an attacker to bypass stack boundary checks, resulting in an out-of-bounds read. By carefully crafting an input, an attacker could pull arbitrary 8-byte values from memory adjacent to the operand stack and interpret them as valid PostScript operands. This primitive, when combined with getinterval and putinterval, could be immediately escalated to arbitrary read/write capabilities across the pagemaker binary's memory space.

Second, a more complex type confusion vulnerability was discovered in the composefont operator. This bug, exacerbated by an incomplete error handling mechanism, allowed the interpreter to misinterpret a name operand as an array operand. This type confusion, when combined with strategic heap spraying, enabled the construction of a robust arbitrary read/write primitive. The ability to control the contents of a "confused array" and populate an output font object's fontdependencyvector with attacker-controlled fake dictionary operands proved to be a powerful exploitation vector.

Together, these vulnerabilities highlight the inherent risks in developing custom language interpreters without rigorous security practices. Despite the presence of some modern mitigations like NX and sandboxing, the lack of PIE (historically) and the complexity of the custom PostScript stack provided ample opportunities for attackers to achieve complete control over the device. The research underscores that the attack surface of networked printers, often overlooked, can be as critical as traditional server or client-side applications.

Technical Deep Dive

▶ Watch: Exploiting index bug with full RELRO bypass (21:00)

The exploitation of Lexmark's PostScript stack hinged on two primary vulnerabilities: an integer overflow in the index operator and a type confusion in composefont. Both demonstrate how subtle flaws in language interpretation can lead to powerful primitives.

Bug 1: Integer Overflow in the index Operator

The index operator's purpose is to duplicate an operand from a specified position on the stack. Its implementation includes a bounds check to ensure the requested index is within the current stack limits. The vulnerability lies in this check: offset < (psob_stack_ptr - bottom_stack_ptr). If offset is a large positive integer, it can integer overflow when subtracted from psob_stack_ptr, causing the result to wrap around to a small positive number. This bypasses the bounds check, allowing the operator to read an 8-byte value from an out-of-bounds memory location and push it onto the operand stack as if it were a valid operand.

Once an arbitrary 8-byte value (e.g., a fake string operand with a controlled pointer) can be placed on the operand stack, the attacker gains a powerful arbitrary read/write primitive. The getinterval operator can read from the address pointed to by the fake string, and putinterval can write to it.

Exploitation with Partial RELRO

With partial RELRO, the Global Offset Table (GOT) is writable, making exploitation straightforward:

  1. Heap Spray: Spray the heap with fake string operands, each containing a pointer to a controlled address.
  2. Out-of-Bounds Read: Use the integer overflow in index to pull one of these fake string operands from the heap onto the operand stack.
  3. Leak memcpy: Use getinterval on the controlled string operand to read the address of memcpy from its entry in the GOT.
  4. Compute system: Calculate the address of system in libc using the leaked memcpy address.
  5. Overwrite GOT: Use putinterval on the controlled string operand to overwrite the memcpy GOT entry with the address of system.
  6. Remote Code Execution (RCE): Trigger a call to memcpy (e.g., by using putinterval with a specially crafted string argument). The argument intended for memcpy's source will instead be passed to system. By crafting this argument as a command string (e.g., "netcat -lvnp 4444 -e /bin/sh"), a bind shell can be established, as netcat is often pre-installed.

Exploitation with Full RELRO

After Lexmark implemented full RELRO, direct GOT overwrites became impossible. While some Pwn2Own contestants reportedly leveraged the free_hook in libc (overwriting it with system and then freeing a controlled chunk), Adams opted for a PostScript-specific approach:

  1. Identify Global Function Pointer: Adams discovered a global function pointer, G_Funk, used by the queryfilterparams operator. This operator's logic is simple: if its argument is of type filter and G_Funk is not null, it executes G_Funk with the argument's value.
  2. Bypass Dispatch Mechanism: The challenge was that queryfilterparams is not directly callable due to Lexmark's operator dispatch mechanism. Operators are called via a hook table in the BSS segment. If an entry exists for an operator in this table, the hooked function is called instead of the original.
  3. Chain of Execution:
  • Leak memcpy (or another function) address from the GOT to determine system's address.
  • Craft a fake hook structure in a writable memory region (e.g., end of BSS). This structure contains a function pointer at offset 8.
  • Overwrite an unused operator's (e.g., anchorsearch) entry in the hook table to point to this fake hook structure.
  • Set the function pointer within the fake hook structure to the address of queryfilterparams.
  • Overwrite the global G_Funk pointer with the address of system.
  • Write the desired command string (e.g., "netcat ...") into a writable memory location (e.g., BSS).
  • Craft a fake filter operand that points to this command string. Use index to place this fake operand onto the stack.
  • Call the anchorsearch operator. This call is redirected by the hook table to queryfilterparams. queryfilterparams then executes G_Funk (now system) with the fake filter operand (the command string), leading to RCE.

Bug 2: Type Confusion in composefont

The composefont operator, introduced in a newer language revision, is designed to create composite fonts. It takes three arguments: a font name, a resource name (to be looked up in a character map), and an array of fonts (dictionaries or font names). The vulnerability arises from a critical error handling flaw:

  1. composefont calls findresource to look up the resource_name (argument 2).
  2. If resource_name is an invalid name (e.g., a garbage string like "NNNN"), findresource triggers an "undefined resource error."
  3. The critical flaw: findresource does not set the global error flag (G_error_occurred), but it leaves its arguments on the stack.
  4. composefont proceeds, assuming no error occurred and that findresource only pushed a single result. It pops one value (the expected result), leaving the garbage resource_name operand on the stack.
  5. composefont then proceeds to parse its third argument (the array of fonts), but now it mistakes the leftover garbage resource_name operand for the array_of_fonts. This is the type confusion.

Exploiting Type Confusion for Arbitrary Read/Write

A name operand (like resource_name) has a value that points to an intermediate structure, which in turn points to the string data. The length field of the operand stores the string's length. When confused as an array operand, its value field is now interpreted as a pointer to a block of operands, and its length field as the number of elements in the array.

  1. Confused Array Interpretation: The intermediate structure of the name operand (e.g., 8 bytes long for a 4-byte string and its length) is now treated as array[0]. Subsequent memory locations adjacent to this intermediate structure are interpreted as array[1], array[2], etc.
  2. Heap Spray for Controlled Elements: composefont iterates through this "confused array" to populate the fontdependencyvector of the output font object.
  • If an element is a dictionary, it's copied directly.
  • If an element is not a dictionary (e.g., an integer), composefont calls findfont on it. Intriguingly, for certain non-name operands like integers, findfont succeeds and returns a reference to "The Courier font" (a valid dictionary).
  1. Crafting the fontdependencyvector: By carefully spraying memory adjacent to the name's intermediate structure with specific hex literals and strings, the attacker can control the "elements" of this confused array. This allows the fontdependencyvector to be populated with a mix of "Courier font" dictionaries (from integer elements) and attacker-controlled fake dictionary operands that point to predictable heap addresses.
  2. Building the Read/Write Primitive:
  • The fake dictionary operands point to a repeating 0x40-byte blob of memory, sprayed predictably (due to 32-bit ASLR and lack of PIE).
  • This blob is structured as a minimal dictionary with a single entry: a key (/edg0) and a value that is an array of two operands.
  • Crucially, the first operand in this two-element array (array[0]) is a string that points to the second operand (array[1]).
  • This creates a self-modifying read/write primitive:
  • An attacker can obtain array[0] (the string pointing to array[1]) onto the PostScript stack.
  • Using putinterval on array[0], the attacker can modify the type, length, and pointer of array[1] in place.
  • By changing array[1]'s pointer to an arbitrary memory address and its type to string, getinterval on array[1] performs an arbitrary read, and putinterval performs an arbitrary write.
  • This primitive is repeatable and controllable entirely from within PostScript, providing a powerful foundation for memory manipulation.

Demo / Proof of Concept

▶ Watch: Second bug: Compose font type confusion (26:00)

While the talk did not feature a live, real-time code demonstration, Aaron Adams effectively conveyed the proof-of-concept for both vulnerabilities through extensive use of GDB debugger views and detailed memory diagrams. These visual aids served as a robust and transparent method to illustrate the internal state changes within the Lexmark PostScript stack and the pagemaker binary's memory.

For the index operator's integer overflow, Adams presented debugger screenshots showing:

  • The initial state of the operand stack with legitimate values.
  • The crafted PostScript code using a large offset for index.
  • The resulting stack state after the index operation, clearly demonstrating an arbitrary 8-byte value (controlled by the attacker via heap spray) appearing on the operand stack, proving the out-of-bounds read.
  • Diagrams further clarified how this controlled operand could then be used with getinterval and putinterval to interact with arbitrary memory regions, such as the GOT.

Similarly, for the composefont type confusion, the demonstration was equally thorough:

  • Debugger views illustrated the operand stack state before and after the findresource call, highlighting how the garbage resource_name operand remained on the stack due to the incomplete error handling.
  • Subsequent views showed composefont interpreting this name operand as an array, with its intermediate structure and adjacent memory being treated as array elements.
  • Memory dumps revealed the contents of the "confused array" and how it was populated with a mix of default font dictionaries and attacker-controlled fake dictionary operands.
  • Adams then showed the structure of the sprayed 0x40-byte dictionary blob, detailing its internal pointers that created the self-modifying read/write primitive. This included how array[0] pointed to array[1], enabling dynamic modification of array[1]'s target.
  • Finally, the process of using this primitive to read from a specific memory address (e.g., the memcpy GOT entry) was visually broken down, showing the manipulation of array[0] to point array[1] to the target, followed by getinterval on array[1] to retrieve the value.

These detailed debugger-based demonstrations, combined with the clear explanations, provided compelling evidence of the vulnerabilities and the efficacy of the described exploitation techniques, effectively serving as a comprehensive technical proof of concept.

Defensive Implications

▶ Watch: Conclusion, future research, and accessibility in security (44:00)

The vulnerabilities exposed in Lexmark's custom PostScript stack carry significant defensive implications for both Lexmark as a vendor and organizations deploying their printers.

  1. Robust Input Validation and Type Safety: The integer overflow in index and the type confusion in composefont underscore a fundamental lack of robust input validation and type checking within the custom PostScript interpreter. Lexmark must implement strict validation for all operator arguments, ensuring bounds checks are impervious to integer overflows and that operand types are rigorously enforced throughout the execution flow.
  2. Comprehensive Error Handling: The composefont type confusion was directly enabled by findresource failing to properly signal an error. All functions within the interpreter must consistently and correctly report error conditions, and callers must diligently check for and handle these errors to maintain consistent program state and prevent logical flaws.
  3. Modern Memory Safety Mitigations:
  • Position Independent Executable (PIE): The historical absence of PIE was a major enabler for these exploits. Lexmark should ensure the pagemaker binary is compiled with PIE to randomize base addresses, significantly raising the bar for memory-guessing attacks like heap spraying, even if memory leaks exist.
  • Stronger ASLR: While 32-bit ASLR has limitations, efforts should be made to maximize entropy for heap, stack, and library randomization to make predictable memory layouts more challenging.
  • Full RELRO: The implementation of full RELRO was a positive step, preventing direct GOT overwrites. This mitigation should be maintained across all binaries.
  1. Reduce Attack Surface: Custom language interpreters are inherently complex and prone to vulnerabilities. Lexmark should review and minimize the set of PostScript operators exposed, ensuring only necessary functionality is available and that these operators are hardened against abuse.
  2. Address Post-Exploitation Paths: Even with sandboxing, the existence of easily exploitable local privilege escalation (LPE) bugs (like those published by blasty) means that a sandbox escape can quickly lead to full system compromise. Lexmark must prioritize fixing these LPEs to make its sandboxing truly effective.
  3. Security Audits and Fuzzing: Given the complexity and custom nature of the PostScript stack, regular, independent security audits and continuous fuzzing with tools specifically designed for language interpreters are crucial to uncover subtle memory corruption and logic bugs.
  4. Vendor Transparency and Researcher Engagement: Lexmark should actively engage with the security research community, respond promptly to reported vulnerabilities, and consider bug bounty programs to encourage responsible disclosure.
  5. User Action: Organizations using Lexmark printers should implement network segmentation to isolate these devices, treat them as critical network endpoints, and apply firmware updates promptly. Regular vulnerability scanning of network devices should include printers.

Key Takeaways

  • Custom Interpreters are High-Risk: Lexmark's custom PostScript stack, a departure from standard implementations, proved to be a complex and vulnerable attack surface, highlighting the inherent risks in bespoke language interpreters.
  • Pwn2Own as a Catalyst: The Pwn2Own competition significantly accelerated public discovery of Lexmark printer vulnerabilities, with a substantial portion found within the PostScript stack.
  • Fundamental Flaws Lead to Powerful Primitives: Basic memory corruption bugs like integer overflows (in index) and type confusions (in composefont), combined with flawed error handling, can quickly escalate to arbitrary read/write primitives.
  • Operand Stack Control is Key: Gaining control over operands on the PostScript stack, whether through out-of-bounds reads or type confusion, is a critical step towards achieving arbitrary memory manipulation and remote code execution.
  • Mitigations are Only as Strong as the Weakest Link: While Lexmark implemented some mitigations (NX, RELRO, sandboxing), the historical lack of PIE and predictable 32-bit ASLR severely weakened the overall security posture, allowing for sophisticated exploitation chains.
  • Printers are Critical Network Endpoints: This research reinforces that networked printers are not benign devices; they are full-fledged Linux systems that require the same level of security scrutiny and hardening as servers or workstations.

About the Speaker(s)

Aaron Adams is a Security Researcher at NCC Group. His work, including the research on Lexmark printers presented here, is notable not only for its technical depth but also for his unique approach to conducting it. Adams performs all of his research using voice control, entirely without a keyboard or mouse. He emphasized this personal detail at the end of his talk, crediting the tools that enable him to work this way and using it as an opportunity to remind fellow researchers about the importance of self-care, good posture, and stretching to mitigate the physical toll of prolonged computer use.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

Solid Pwn2Own-derived printer exploitation work with two real bugs and real chains. Adams shows the gritty details of turning an integer overflow and a type confusion into arb r/w, and the full-RELRO bypass via GFunk is genuinely clever. Not field-defining, but this is exactly the kind of deep-dive that teaches people how exploitation actually works.

Heather Calloway (CISO) — SOLID

Strong offensive research on a custom PostScript interpreter in Lexmark printers, demonstrating integer overflow and type confusion bugs leading to RCE. Technically competent Pwn2Own work, but the strategic implications for most enterprise security programs are limited — printers are a real attack surface, but this specific research is more useful for vuln researchers than defenders.

→ Top-rated talks at HITBSecConf2023 - Phuket

All talks from HITBSecConf2023 - Phuket