Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: RUB-NDS/PRET
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: xfox64x/PRET
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.
  • 20 commits
  • 10 files changed
  • 1 contributor

Commits on Aug 31, 2018

  1. Update pret.py

    Added new logging method and supporting argument. Removed some characters that don't render well on Windows.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    377d048 View commit details
  2. Update helper.py

    Added a new Logger class that contains stateful elements and combines functions from output() and log(). Said functions have been re-written to log the PRET shell as it's rendered and used interactively. Strips out color codes and handles some log formatting.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7c4c311 View commit details
  3. Update printer.py

    Modified everything to work with the new Logger class and changed a bunch of other things I can't remember right now...
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6fd273a View commit details
  4. Update postscript.py

    Added support for new Logger class, fixed outstanding issue in capture, and made a bunch of other changes I don't remember...
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1f84d76 View commit details
  5. Update pjl.py

    Added support for new Logger class and some other minor changes.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a37ee82 View commit details
  6. Update pcl.py

    Added support for new Logger class and other minor changes.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9eb4d2d View commit details
  7. Update discovery.py

    Added support for new Logger class and minor other changes.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6338541 View commit details
  8. Update capabilities.py

    Added support for new Logger class and minor other changes.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    44b3abf View commit details
  9. Update README.md

    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    bf18d6c View commit details
  10. Update README.md

    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    648a2c9 View commit details
  11. Update capabilities.py

    Added HTTPResponse patch for odd one-off cases where the HTTP response is messed up.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ee89f72 View commit details
  12. Update helper.py

    Fixed random output.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7e555df View commit details
  13. Update capabilities.py

    Added printerModelDatabase class so the supported device databases can be updated with new devices (only works for PS and PJL).
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    996eca1 View commit details
  14. Update pjl.py

    Added support for the printerModelDatabase class so new supported device models can be added to the PJL database on successful do_id.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    16a90e2 View commit details
  15. Update postscript.py

    Added support for the printerModelDatabase class so new supported device models can be added to the PS database on successful do_id's.
    xfox64x authored Aug 31, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    aed2ba1 View commit details

Commits on Oct 12, 2018

  1. Update capabilities.py

    Fixed errors, improved validations/error-handling, updated HTTP and IPP to handle SSL upgrading on redirect, added urllib context to ignore printer certs, improved the overall flow and output of the safety mode to try every identification method and actually validate successful device identification, increased the default timeouts for identification methods (seen some slow ass printers that wouldn't consistently identify).
    xfox64x authored Oct 12, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3daceb8 View commit details
  2. Update postscript.py

    Commented, and now properly displaying if a printer's model was successfully added to the appropriate lang database.
    xfox64x authored Oct 12, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3f884f6 View commit details
  3. Update printer.py

    Fixed an error, and inserted a bunch of newlines for no apparent reason.
    xfox64x authored Oct 12, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    120d7fa View commit details
  4. Update pret.py

    Stopped passing the "quiet" command line argument to the ASCII art printing function because why...
    xfox64x authored Oct 12, 2018

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0db8484 View commit details

Commits on Nov 25, 2019

  1. Create FUNDING.yml

    xfox64x authored Nov 25, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4a04f95 View commit details
Showing with 1,068 additions and 412 deletions.
  1. +1 −0 .github/FUNDING.yml
  2. +13 −2 README.md
  3. +169 −46 capabilities.py
  4. +6 −6 discovery.py
  5. +408 −67 helper.py
  6. +23 −18 pcl.py
  7. +78 −65 pjl.py
  8. +127 −108 postscript.py
  9. +21 −17 pret.py
  10. +222 −83 printer.py
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
patreon: forrestsux
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
## PRET - Printer Exploitation Toolkit
Heavily modified fork of PRET done mainly to fix issues when running on Windows and enhance logging capabilities. Logging now contains timestampped command execution, rendered as seen in the PRET shell. Future plans include:
- Fixing HTTP/S errors.
- Improving printer fingerprinting.
- Adding PRET shell commands to do SNMP/nmap/other scans.
- Attacks on other protocols.
- Exploit integration.
- Target management and tracking.

**Is your printer secure? Check before someone else does...**

@@ -25,7 +32,10 @@ For experimental, ‘driverless’ printing (see print command), ImageMagick and
### Usage

```
usage: pret.py [-h] [-s] [-q] [-d] [-i file] [-o file] target {ps,pjl,pcl}
usage: pret.py [-h] [-s] [-q] [-d] [-i file] [-o file | -O]
target {ps,pjl,pcl}
Printer Exploitation Toolkit.
positional arguments:
target printer device or hostname
@@ -37,7 +47,8 @@ optional arguments:
-q, --quiet suppress warnings and chit-chat
-d, --debug enter debug mode (show traffic)
-i file, --load file load and run commands from file
-o file, --log file log raw data sent to the target
-o file, --log file Log command line session to specified file.
-O, --Log Log command line session to <cwd>\<target>_PRET_Log.txt
```

###### Example usage:
215 changes: 169 additions & 46 deletions capabilities.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

# python standard library
import re, os, sys, urllib2
import re, os, sys, urllib2, httplib, ssl

# local pret classes
from helper import output, item
@@ -12,11 +12,71 @@
except ImportError:
pass

def HTTPResponsePatch(func):
def inner(*args):
try:
return func(*args)
except httplib.IncompleteRead, e:
return e.partial
return inner

httplib.HTTPResponse.read = HTTPResponsePatch(httplib.HTTPResponse.read)

class printerModelDatabase():

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get the path to the correct database of supported devices
def get_database_path(self, mode):
return os.path.join(os.path.join(os.path.dirname(os.path.realpath(__file__)), "db"), (mode + ".dat"))

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# open database of supported devices
def get_models(self, mode):
try:
with open(printerModelDatabase().get_database_path(mode), 'r') as f:
models = filter(None, (line.strip() for line in f))
return models
except IOError as e:
output().errmsg_("Cannot open file", e)
return []
return []

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get models matching the supplied model, from the database of supported devices
def get_matching_models(self, mode, model):
#matches = filter(None, [re.findall(re.escape(m), model, re.I) for m in printerModelDatabase().get_models(mode)])
#print("Mode: "+mode)
#print("Model: "+model)
#print(matches)
#return matches
return filter(None, [re.findall(re.escape(m), model, re.I) for m in printerModelDatabase().get_models(mode)])

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# add model to database of supported devices
def add_model(self, mode, model):
if model and len(printerModelDatabase().get_matching_models(mode, model)) == 0 and len(model.strip()) > 0:
try:
with open(printerModelDatabase().get_database_path(mode), 'a') as f:
f.write("%s\n" % str(model))
return True

except IOError as e:
output().errmsg_("Cannot open file", e)
return False
else:
return False


class capabilities():
# set defaults
support = False
# be quick and dirty
timeout = 1.5
# Hold the supplied mode:
mode = ""

# Create an array for supported models
support = []

# Let's not be quick - printers be slow.
timeout = 5

# set pret.py directory
rundir = os.path.dirname(os.path.realpath(__file__)) + os.path.sep
'''
@@ -34,99 +94,156 @@ class capabilities():
def __init__(self, args):
# skip this in unsafe mode
if not args.safe: return

# Save off the mode argument.
self.mode = args.mode

# set printer language
if args.mode == 'ps': lang = ["PS", "PostScript", "BR-Script", "KPDL"]
if args.mode == 'pjl': lang = ["PJL"]
if args.mode == 'pcl': lang = ["PCL"]
if self.mode == 'ps': lang = ["PS", "PostScript", "BR-Script", "KPDL"]
if self.mode == 'pjl': lang = ["PJL"]
if self.mode == 'pcl': lang = ["PCL"]

# get list of PostScript/PJL/PCL capable printers
self.models = self.get_models(args.mode + ".dat")
self.models = self.get_models(self.mode + ".dat")

# try to get printer capabilities via IPP/SNMP/HTTP
if not self.support: self.ipp(args.target, lang)
if not self.support: self.http(args.target)
if not self.support: self.snmp(args.target, lang)
self.ipp(args.target, lang)

self.http(args.target)

self.snmp(args.target, lang)

# feedback on PostScript/PJL/PCL support
self.feedback(self.support, lang[0])

# in safe mode, exit if unsupported
if args.safe and not self.support:
if args.safe and len(self.support) == 0:
print(os.linesep + "Quitting as we are in safe mode.")
sys.exit()
print("")

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get capabilities via IPP
def ipp(self, host, lang):

# Define model and langs, from the start, as empty strings.
model = ""
langs = ""

sys.stdout.write("Checking for IPP support: ")

try:
# poor man's way to get printer info via IPP
sys.stdout.write("Checking for IPP support: ")
body = ("\x01\x01\x00\x0b\x00\x01\xab\x10\x01G\x00\x12attributes-charset\x00\x05utf-8H"
+ "\x00\x1battributes-natural-language\x00\x02enE\x00\x0bprinter-uri\x00\x14ipp:"
+ "//localhost/ipp/D\x00\x14requested-attributes\x00\x13printer-description\x03")
request = urllib2.Request("http://" + host + ":631/",
data=body, headers={'Content-type': 'application/ipp'})
response = urllib2.urlopen(request, timeout=self.timeout).read()
# get name of device
model = item(re.findall("MDL:(.+?);", response)) # e.g. MDL:hp LaserJet 4250
# get language support
langs = item(re.findall("CMD:(.+?);", response)) # e.g. CMD:PCL,PJL,POSTSCRIPT
self.support = filter(None, [re.findall(re.escape(pdl), langs, re.I) for pdl in lang])
self.set_support(model)
output().green("found")

request = urllib2.Request("http://" + host + ":631/", data=body, headers={'Content-type': 'application/ipp'})

# Manually doubling the timeout because this seems to take a while for the printers that it works on.
# Added SSL context that should avoid validating any printer certificates, just in case connection upgrading happens.
response = urllib2.urlopen(request, timeout=(self.timeout * 2), context=ssl._create_unverified_context()).read()

# get name of device using regex. Now checking if the regex is successful before assuming it was.
modelRegexResults = item(re.findall("MDL:(.+?);", response)) # e.g. MDL:hp LaserJet 4250
if modelRegexResults and modelRegexResults != "":
model = modelRegexResults

# get language support. Now checking if the regex is successful before assuming it was.
languageRegexResults = item(re.findall("CMD:(.+?);", response)) # e.g. CMD:PCL,PJL,POSTSCRIPT
if languageRegexResults and languageRegexResults != "":
langs = languageRegexResults

# Not really sure what the point of this line is - the data set doesn't really get used, making langs and lang pointless.
# It's only there to maintain the fact that some variant of PS was found, but it probably shouldn't mix with the other data.
languageCheck = filter(None, [re.findall(re.escape(pdl), langs, re.I) for pdl in lang])
if len(languageCheck) > 0:
self.support += languageCheck

if self.set_support(model):
output().green_("found [%s]" % (model))
else:
output().errmsg_("not found", "check successful but no data")

except Exception as e:
output().errmsg("not found", e)
output().errmsg_("not found", e)

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get capabilities via HTTP
def http(self, host):
sys.stdout.write("Checking for HTTP support: ")

try:
# poor man's way get http title
sys.stdout.write("Checking for HTTP support: ")
html = urllib2.urlopen("http://" + host, timeout=self.timeout).read()
# cause we are to parsimonious to import BeautifulSoup ;)
title = re.findall("<title.*?>\n?(.+?)\n?</title>", html, re.I|re.M|re.S)
# get name of device
model = item(title)
# get language support
self.set_support(model)
output().green("found")
# Get the first 5k bytes of the printer config page and hope that the title is in it.
# Urllib was failing to get all of some config pages and would return a random section of content from the specified page...
# Added SSL context that should avoid validating any printer certificates, just in case connection upgrading happens.
html = urllib2.urlopen("http://" + host, timeout=self.timeout, context=ssl._create_unverified_context()).read(5000)

# I'm not entirely sure what this original comment meant, but Regex is the way to go.
titleMatchObj = re.search("<title.*?>\s*(?P<TitleGroup>[a-zA-Z0-9 \.-_/]+).*?</title>", html, re.I|re.M|re.S)

# Also more validation and better discovery.
if titleMatchObj:
# get name of device and check for language support
self.set_support(titleMatchObj.group("TitleGroup"))
output().green_("found [%s]" % (titleMatchObj.group("TitleGroup")))
else:
output().errmsg_("not found", "check successful but no data")

except Exception as e:
output().errmsg("not found", e)
output().errmsg_("not found", e)

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# get capabilities via SNMP
def snmp(self, host, lang):
sys.stdout.write("Checking for SNMP support: ")

try:
sys.stdout.write("Checking for SNMP support: ")
# query device description and supported languages
desc, desc_oid = [], '1.3.6.1.2.1.25.3.2.1.3' # HOST-RESOURCES-MIB → hrDeviceDescr
pdls, pdls_oid = [], '1.3.6.1.2.1.43.15.1.1.5.1' # Printer-MIB → prtInterpreterDescription

error, error_status, idx, binds = cmdgen.CommandGenerator().nextCmd(
cmdgen.CommunityData('public', mpModel=0), cmdgen.UdpTransportTarget(
(host, 161), timeout=self.timeout, retries=0), desc_oid, pdls_oid)

# exit on error
if error: raise Exception(error)
if error_status: raise Exception(error_status.prettyPrint())

# parse response
for row in binds:
for key, val in row:
if desc_oid in str(key): desc.append(str(val))
if pdls_oid in str(key): pdls.append(str(val))

# get name of device
model = item(desc)

# get language support
langs = ','.join(pdls)
self.support = filter(None, [re.findall(re.escape(pdl), langs, re.I) for pdl in lang])
self.set_support(model)
output().green("found")

languageCheck = filter(None, [re.findall(re.escape(pdl), langs, re.I) for pdl in lang])
if len(languageCheck) > 0:
self.support += languageCheck

if self.set_support(model):
output().green_("found [%s]" % (model))
else:
output().errmsg_("not found", "check successful but no data")

except NameError:
output().errmsg("not found", "pysnmp module not installed")
output().errmsg_("not found", "pysnmp module not installed")

except Exception as e:
output().errmsg("not found", e)
output().errmsg_("not found", e)

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# feedback on language support
def feedback(self, support, lang):
sys.stdout.write("Checking for %-21s" % (lang + " support: "))
if support: output().green("found")
else: output().warning("not found")
if support: output().green_("found")
else: output().warning_("not found")

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# set language support
@@ -141,7 +258,13 @@ def set_support(self, model):
│ 'd' (duplex), 't' (tray), 'c' (color), 'n' (network). │
└───────────────────────────────────────────────────────┘
'''
self.support = filter(None, [re.findall(re.escape(m), model, re.I) for m in self.models])
if model and model != "":
returnValues = printerModelDatabase().get_matching_models(self.mode, model)
if len(returnValues) > 0:
self.support += returnValues
#print(self.support)
return True
return False

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# open database of supported devices
@@ -152,5 +275,5 @@ def get_models(self, file):
f.close()
return models
except IOError as e:
output().errmsg("Cannot open file", e)
output().errmsg_("Cannot open file", e)
return []
12 changes: 6 additions & 6 deletions discovery.py
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ class discovery():
def __init__(self, usage=False):
# abort if pysnmp is not installed
if not snmp_modules_found:
output().warning("Please install the 'pysnmp' module for SNMP support.")
output().warning_("Please install the 'pysnmp' module for SNMP support.")
if usage: print("")
return
# skip when running 'discover' in interactive mode
@@ -106,13 +106,13 @@ def __init__(self, usage=False):
# list found network printers
if results:
print("")
output().discover(('address', ('device', 'uptime', 'status', None)))
output().hline(79)
output().discover_(('address', ('device', 'uptime', 'status', None)))
output().hline_(79)
for printer in sorted(results.items(), key=lambda item: socket.inet_aton(item[0])):
output().discover(printer)
output().discover_(printer)
else:
output().info("No printers found via SNMP broadcast")
output().info_("No printers found via SNMP broadcast")
if usage or results: print("")
except Exception as e:
output().errmsg("SNMP Error", e)
output().errmsg_("SNMP Error", e)
if usage: print("")
Loading